microcad_lang_base/src_ref/
mod.rs1mod 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
54 .as_ref()
55 .map(|s| SourceSpan::new(s.range.start.into(), s.range.len()))
56 }
57
58 pub fn with_line_offset(self, offset: usize) -> Self {
60 Self(self.0.map(|inner| Box::new(inner.with_line_offset(offset))))
61 }
62}
63
64impl From<SrcRef> for SourceSpan {
65 fn from(value: SrcRef) -> Self {
66 value
67 .as_miette_span()
68 .unwrap_or(SourceSpan::new(0.into(), 0))
69 }
70}
71
72#[derive(Clone, Default)]
74pub struct SrcRefInner {
75 pub range: std::ops::Range<usize>,
77 pub at: LineCol,
79 pub source_file_hash: u64,
81}
82
83impl SrcRefInner {
84 pub fn is_overlapping(&self, other: &Self) -> bool {
86 self.source_file_hash != 0
87 && other.source_file_hash != 0
88 && (self.range.start < other.range.end)
89 && (other.range.start < self.range.end)
90 }
91
92 pub fn with_line_offset(&self, line_offset: usize) -> Self {
94 let mut s = self.clone();
95 s.at.line += line_offset;
96 s
97 }
98}
99
100impl From<SrcRefInner> for SourceSpan {
101 fn from(value: SrcRefInner) -> Self {
102 SourceSpan::new(value.range.start.into(), value.range.len())
103 }
104}
105
106impl std::fmt::Display for SrcRef {
107 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
108 match &self.0 {
109 Some(s) => write!(f, "{}", s.at),
110 _ => write!(f, crate::invalid_no_ansi!(REF)),
111 }
112 }
113}
114
115impl std::fmt::Debug for SrcRef {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 match &self.0 {
118 Some(s) => write!(
119 f,
120 "{} ({}..{}) in {:#x}",
121 s.at, s.range.start, s.range.end, s.source_file_hash
122 ),
123 _ => write!(f, crate::invalid!(REF)),
124 }
125 }
126}
127
128impl PartialEq for SrcRef {
129 fn eq(&self, _: &Self) -> bool {
130 true
131 }
132}
133
134impl PartialOrd for SrcRef {
135 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
136 Some(self.cmp(other))
137 }
138}
139
140impl Eq for SrcRef {}
141
142impl Ord for SrcRef {
143 fn cmp(&self, _: &Self) -> std::cmp::Ordering {
144 std::cmp::Ordering::Equal
145 }
146}
147
148impl SrcRef {
149 pub fn len(&self) -> usize {
151 self.0.as_ref().map(|s| s.range.len()).unwrap_or(0)
152 }
153
154 #[must_use]
156 pub fn is_empty(&self) -> bool {
157 self.len() == 0
158 }
159
160 pub fn source_hash(&self) -> u64 {
166 self.0.as_ref().map(|s| s.source_file_hash).unwrap_or(0)
167 }
168
169 pub fn source_slice<'a>(&self, src: &'a str) -> &'a str {
171 &src[self.0.as_ref().expect("SrcRef").range.to_owned()]
172 }
173
174 pub fn merge(lhs: &impl SrcReferrer, rhs: &impl SrcReferrer) -> SrcRef {
181 match (lhs.src_ref(), rhs.src_ref()) {
182 (SrcRef(Some(lhs)), SrcRef(Some(rhs))) => {
183 if lhs.source_file_hash == rhs.source_file_hash {
184 let source_file_hash = lhs.source_file_hash;
185
186 if lhs.range == rhs.range {
187 SrcRef(Some(lhs))
188 } else if lhs.range.end > rhs.range.start || lhs.range.start > rhs.range.end {
189 log::warn!(
190 "ranges not in correct order: {lhs} vs {rhs} @ {source_file_hash}",
191 lhs = lhs.at,
192 rhs = rhs.at
193 );
194 SrcRef(None)
195 } else {
196 SrcRef(Some(Box::new(SrcRefInner {
197 range: {
198 assert!(lhs.range.end <= rhs.range.end);
200 assert!(lhs.range.start <= rhs.range.start);
201
202 lhs.range.start..rhs.range.end
203 },
204 at: lhs.at,
205 source_file_hash,
206 })))
207 }
208 } else {
209 log::warn!("references are not in the same file");
210 SrcRef(None)
211 }
212 }
213 (SrcRef(Some(hs)), SrcRef(None)) | (SrcRef(None), SrcRef(Some(hs))) => SrcRef(Some(hs)),
214 _ => SrcRef(None),
215 }
216 }
217
218 pub fn merge_all<S: SrcReferrer>(referrers: impl Iterator<Item = S>) -> SrcRef {
222 let mut result = SrcRef(None);
223 for referrer in referrers {
224 if let Some(src_ref) = referrer.src_ref().0 {
225 if let SrcRef(Some(result)) = &mut result {
226 if result.source_file_hash != src_ref.source_file_hash {
227 panic!("can only merge source references of the same file");
228 }
229 if src_ref.range.start < result.range.start {
230 result.range.start = src_ref.range.start;
231 result.at = src_ref.at;
232 }
233 result.range.end = std::cmp::max(src_ref.range.end, result.range.end);
234 } else {
235 result = SrcRef(Some(src_ref));
236 }
237 }
238 }
239 result
240 }
241
242 pub fn at(&self) -> Option<LineCol> {
244 self.0.as_ref().map(|s| s.at.clone())
245 }
246 pub fn is_overlapping(&self, other: &Self) -> bool {
250 match (&self.0, &other.0) {
251 (Some(a), Some(b)) => a.is_overlapping(b),
252 _ => false,
253 }
254 }
255
256 pub fn line(&self) -> Option<usize> {
258 self.0.as_ref().map(|inner| inner.at.line)
259 }
260
261 pub fn col(&self) -> Option<usize> {
263 self.0.as_ref().map(|inner| inner.at.line)
264 }
265}
266
267#[test]
268fn merge_all() {
269 use std::ops::Range;
270 assert_eq!(
271 SrcRef::merge_all(
272 [
273 SrcRef::new(Range { start: 5, end: 8 }, 1, 6, 123),
274 SrcRef::new(Range { start: 8, end: 10 }, 2, 1, 123),
275 SrcRef::new(Range { start: 12, end: 16 }, 3, 1, 123),
276 SrcRef::new(Range { start: 0, end: 10 }, 1, 1, 123),
277 ]
278 .iter(),
279 ),
280 SrcRef::new(Range { start: 0, end: 16 }, 1, 1, 123),
281 );
282}
283
284#[test]
285fn test_src_ref() {
286 let input = "geo3d::Cube(size_x = 3.0, size_y = 3.0, size_z = 3.0);";
287
288 let cube = 7..11;
289 let size_y = 26..32;
290
291 let cube = SrcRef::new(cube, 1, 0, 0);
292 let size_y = SrcRef::new(size_y, 1, 0, 0);
293
294 assert_eq!(cube.source_slice(input), "Cube");
295 assert_eq!(size_y.source_slice(input), "size_y");
296}