microcad_lang_base/src_ref/
mod.rs1mod line_col;
17mod line_index;
18mod refer;
19mod src_referrer;
20
21pub use line_col::*;
22pub use line_index::*;
23pub use refer::*;
24pub use src_referrer::*;
25
26use crate::HashId;
27
28use miette::SourceSpan;
29
30pub type Span = std::ops::Range<usize>;
32
33#[derive(Clone, Default)]
37pub struct SrcRef {
38 pub range: Span,
40 pub at: LineCol,
42 pub source_hash: HashId,
44}
45
46impl SrcRef {
47 pub fn new(range: std::ops::Range<usize>, at: LineCol, source_hash: HashId) -> Self {
52 Self {
53 range,
54 at,
55 source_hash,
56 }
57 }
58
59 pub fn none() -> Self {
60 Self::default()
61 }
62
63 pub fn as_miette_span(&self) -> Option<SourceSpan> {
65 if self.is_some() {
66 Some(SourceSpan::new(self.range.start.into(), self.range.len()))
67 } else {
68 None
69 }
70 }
71
72 pub fn is_none(&self) -> bool {
73 self.source_hash == 0
74 }
75
76 pub fn is_some(&self) -> bool {
77 !self.is_none()
78 }
79
80 pub fn is_overlapping(&self, other: &Self) -> bool {
84 self.is_some()
85 && other.is_some()
86 && (self.range.start < other.range.end)
87 && (other.range.start < self.range.end)
88 }
89
90 pub fn with_line_offset(&self, line_offset: u32) -> Self {
92 let mut s = self.clone();
93 s.at.line += line_offset;
94 s
95 }
96
97 pub fn len(&self) -> usize {
99 self.range.len()
100 }
101
102 #[must_use]
104 pub fn is_empty(&self) -> bool {
105 self.len() == 0
106 }
107
108 pub fn source_hash(&self) -> u64 {
114 self.source_hash
115 }
116
117 pub fn source_slice<'a>(&self, src: &'a str) -> &'a str {
119 assert!(self.is_some());
120 &src[self.range.to_owned()]
121 }
122
123 pub fn merge(lhs: &impl SrcReferrer, rhs: &impl SrcReferrer) -> SrcRef {
130 let lhs = lhs.src_ref();
131 let rhs = rhs.src_ref();
132
133 match (lhs.is_some(), rhs.is_some()) {
134 (true, true) => {
135 if lhs.source_hash == rhs.source_hash {
136 let source_hash = lhs.source_hash;
137
138 if lhs.range == rhs.range {
139 lhs
140 } else if lhs.range.end > rhs.range.start || lhs.range.start > rhs.range.end {
141 log::warn!(
142 "ranges not in correct order: {lhs} vs {rhs} @ {source_hash}",
143 lhs = lhs.at,
144 rhs = rhs.at
145 );
146 SrcRef::none()
147 } else {
148 SrcRef {
149 range: {
150 assert!(lhs.range.end <= rhs.range.end);
152 assert!(lhs.range.start <= rhs.range.start);
153
154 lhs.range.start..rhs.range.end
155 },
156 at: lhs.at,
157 source_hash,
158 }
159 }
160 } else {
161 log::warn!("references are not in the same file");
162 SrcRef::none()
163 }
164 }
165 (true, false) => lhs.clone(),
166 (false, true) => rhs.clone(),
167 (false, false) => SrcRef::none(),
168 }
169 }
170
171 pub fn merge_all<S: SrcReferrer>(referrers: impl Iterator<Item = S>) -> SrcRef {
175 let mut result = SrcRef::none();
176 for referrer in referrers {
177 let src_ref = referrer.src_ref();
178 if src_ref.is_some() {
179 if result.is_some() {
180 if result.source_hash != src_ref.source_hash {
181 panic!("can only merge source references of the same file");
182 }
183 if src_ref.range.start < result.range.start {
184 result.range.start = src_ref.range.start;
185 result.at = src_ref.at;
186 }
187 result.range.end = std::cmp::max(src_ref.range.end, result.range.end);
188 } else {
189 result = src_ref;
190 }
191 }
192 }
193 result
194 }
195
196 pub fn at(&self) -> Option<LineCol> {
198 if self.is_some() {
199 Some(self.at.clone())
200 } else {
201 None
202 }
203 }
204
205 pub fn line(&self) -> Option<u32> {
207 if self.is_some() {
208 Some(self.at.line)
209 } else {
210 None
211 }
212 }
213
214 pub fn col(&self) -> Option<u32> {
216 if self.is_some() {
217 Some(self.at.col)
218 } else {
219 None
220 }
221 }
222}
223
224impl From<SrcRef> for SourceSpan {
225 fn from(value: SrcRef) -> Self {
226 value
227 .as_miette_span()
228 .unwrap_or(SourceSpan::new(0.into(), 0))
229 }
230}
231
232impl std::fmt::Display for SrcRef {
233 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
234 match &self.is_some() {
235 true => write!(f, "{}", self.at),
236 false => write!(f, "<NO REF>"),
237 }
238 }
239}
240
241impl std::fmt::Debug for SrcRef {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 match &self.is_some() {
244 true => write!(
245 f,
246 "{} ({}..{}) in {:#x}",
247 self.at, self.range.start, self.range.end, self.source_hash
248 ),
249 false => write!(f, "<NO REF>"),
250 }
251 }
252}
253
254impl PartialEq for SrcRef {
255 fn eq(&self, _: &Self) -> bool {
256 true
257 }
258}
259
260impl PartialOrd for SrcRef {
261 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
262 Some(self.cmp(other))
263 }
264}
265
266impl Eq for SrcRef {}
267
268impl Ord for SrcRef {
269 fn cmp(&self, _: &Self) -> std::cmp::Ordering {
270 std::cmp::Ordering::Equal
271 }
272}
273
274#[test]
275fn merge_all() {
276 use std::ops::Range;
277 assert_eq!(
278 SrcRef::merge_all(
279 [
280 SrcRef::new(Range { start: 5, end: 8 }, LineCol { line: 1, col: 6 }, 123),
281 SrcRef::new(
282 Range { start: 8, end: 10 },
283 LineCol { line: 2, col: 1 },
284 123
285 ),
286 SrcRef::new(
287 Range { start: 12, end: 16 },
288 LineCol { line: 3, col: 1 },
289 123
290 ),
291 SrcRef::new(
292 Range { start: 0, end: 10 },
293 LineCol { line: 1, col: 1 },
294 123
295 ),
296 ]
297 .iter(),
298 ),
299 SrcRef::new(
300 Range { start: 0, end: 16 },
301 LineCol { line: 1, col: 1 },
302 123
303 ),
304 );
305}
306
307#[test]
308fn test_src_ref() {
309 use microcad_core::hash::{ComputedHash, Hashed};
310 let input = Hashed::new("geo3d::Cube(size_x = 3.0, size_y = 3.0, size_z = 3.0);");
311
312 let cube = 7..11;
313 let size_y = 26..32;
314 let cube = SrcRef::new(cube, LineCol { line: 1, col: 0 }, input.computed_hash());
315 let size_y = SrcRef::new(size_y, LineCol { line: 1, col: 0 }, input.computed_hash());
316
317 assert_eq!(cube.source_slice(input.value()), "Cube");
318 assert_eq!(size_y.source_slice(input.value()), "size_y");
319}