1use std::ops::Range;
2use std::rc::Rc;
3
4use ahash::AHashSet;
5
6use crate::slice_helpers::zero_slice;
7use crate::templaters::base::TemplatedFile;
8
9#[derive(Debug, Clone)]
24pub struct PositionMarker {
25 data: Rc<PositionMarkerData>,
26}
27
28impl std::ops::Deref for PositionMarker {
29 type Target = PositionMarkerData;
30
31 fn deref(&self) -> &Self::Target {
32 &self.data
33 }
34}
35
36impl std::ops::DerefMut for PositionMarker {
37 fn deref_mut(&mut self) -> &mut Self::Target {
38 Rc::make_mut(&mut self.data)
39 }
40}
41
42impl Eq for PositionMarker {}
43
44#[derive(Debug, Clone)]
45pub struct PositionMarkerData {
46 pub source_slice: Range<usize>,
47 pub templated_slice: Range<usize>,
48 pub templated_file: TemplatedFile,
49 pub working_line_no: usize,
50 pub working_line_pos: usize,
51}
52
53impl Default for PositionMarker {
54 fn default() -> Self {
55 Self {
56 data: PositionMarkerData {
57 source_slice: 0..0,
58 templated_slice: 0..0,
59 templated_file: "".to_string().into(),
60 working_line_no: 0,
61 working_line_pos: 0,
62 }
63 .into(),
64 }
65 }
66}
67
68impl PositionMarker {
69 pub fn new(
73 source_slice: Range<usize>,
74 templated_slice: Range<usize>,
75 templated_file: TemplatedFile,
76 working_line_no: Option<usize>,
77 working_line_pos: Option<usize>,
78 ) -> Self {
79 match (working_line_no, working_line_pos) {
80 (Some(working_line_no), Some(working_line_pos)) => Self {
81 data: PositionMarkerData {
82 source_slice,
83 templated_slice,
84 templated_file,
85 working_line_no,
86 working_line_pos,
87 }
88 .into(),
89 },
90 _ => {
91 let (working_line_no, working_line_pos) =
92 templated_file.get_line_pos_of_char_pos(templated_slice.start, false);
93 Self {
94 data: PositionMarkerData {
95 source_slice,
96 templated_slice,
97 templated_file,
98 working_line_no,
99 working_line_pos,
100 }
101 .into(),
102 }
103 }
104 }
105 }
106
107 #[track_caller]
108 pub fn source_str(&self) -> &str {
109 &self.templated_file.source_str[self.source_slice.clone()]
110 }
111
112 pub fn line_no(&self) -> usize {
113 self.source_position().0
114 }
115
116 pub fn line_pos(&self) -> usize {
117 self.source_position().1
118 }
119
120 #[track_caller]
121 pub fn from_child_markers<'a>(
122 markers: impl Iterator<Item = &'a PositionMarker>,
123 ) -> PositionMarker {
124 let mut source_start = usize::MAX;
125 let mut source_end = usize::MIN;
126 let mut template_start = usize::MAX;
127 let mut template_end = usize::MIN;
128 let mut templated_files = AHashSet::new();
129
130 for marker in markers {
131 source_start = source_start.min(marker.source_slice.start);
132 source_end = source_end.max(marker.source_slice.end);
133 template_start = template_start.min(marker.templated_slice.start);
134 template_end = template_end.max(marker.templated_slice.end);
135 templated_files.insert(marker.templated_file.clone());
136 }
137
138 if templated_files.len() != 1 {
139 panic!("Attempted to make a parent marker from multiple files.");
140 }
141
142 let templated_file = templated_files.into_iter().next().unwrap();
143 PositionMarker::new(
144 source_start..source_end,
145 template_start..template_end,
146 templated_file,
147 None,
148 None,
149 )
150 }
151
152 pub fn source_position(&self) -> (usize, usize) {
154 self.templated_file
155 .get_line_pos_of_char_pos(self.templated_slice.start, true)
156 }
157
158 pub fn templated_position(&self) -> (usize, usize) {
160 self.templated_file
161 .get_line_pos_of_char_pos(self.templated_slice.start, false)
162 }
163
164 pub fn working_loc_after(&self, raw: &str) -> (usize, usize) {
165 Self::infer_next_position(raw, self.working_line_no, self.working_line_pos)
166 }
167
168 pub fn infer_next_position(raw: &str, line_no: usize, line_pos: usize) -> (usize, usize) {
171 if raw.is_empty() {
172 return (line_no, line_pos);
173 }
174 let split: Vec<&str> = raw.split('\n').collect();
175 (
176 line_no + (split.len() - 1),
177 if split.len() == 1 {
178 line_pos + raw.len()
179 } else {
180 split.last().unwrap().len() + 1
181 },
182 )
183 }
184
185 pub fn working_loc(&self) -> (usize, usize) {
187 (self.working_line_no, self.working_line_pos)
188 }
189
190 pub fn from_point(
192 source_point: usize,
193 templated_point: usize,
194 templated_file: TemplatedFile,
195 working_line_no: Option<usize>,
196 working_line_pos: Option<usize>,
197 ) -> Self {
198 Self::new(
199 zero_slice(source_point),
200 zero_slice(templated_point),
201 templated_file,
202 working_line_no,
203 working_line_pos,
204 )
205 }
206
207 pub fn start_point_marker(&self) -> PositionMarker {
209 PositionMarker::from_point(
210 self.source_slice.start,
211 self.templated_slice.start,
212 self.templated_file.clone(),
213 Some(self.working_line_no),
215 Some(self.working_line_pos),
216 )
217 }
218
219 pub fn end_point_marker(&self) -> PositionMarker {
220 PositionMarker::from_point(
222 self.source_slice.end,
223 self.templated_slice.end,
224 self.templated_file.clone(),
225 None,
226 None,
227 )
228 }
229
230 pub fn is_literal(&self) -> bool {
247 self.templated_file
248 .is_source_slice_literal(&self.source_slice)
249 }
250
251 pub fn from_points(
252 start_point_marker: &PositionMarker,
253 end_point_marker: &PositionMarker,
254 ) -> PositionMarker {
255 Self {
256 data: PositionMarkerData {
257 source_slice: start_point_marker.source_slice.start
258 ..end_point_marker.source_slice.end,
259 templated_slice: start_point_marker.templated_slice.start
260 ..end_point_marker.templated_slice.end,
261 templated_file: start_point_marker.templated_file.clone(),
262 working_line_no: start_point_marker.working_line_no,
263 working_line_pos: start_point_marker.working_line_pos,
264 }
265 .into(),
266 }
267 }
268
269 pub(crate) fn with_working_position(
270 mut self,
271 line_no: usize,
272 line_pos: usize,
273 ) -> PositionMarker {
274 self.working_line_no = line_no;
275 self.working_line_pos = line_pos;
276 self
277 }
278
279 pub(crate) fn is_point(&self) -> bool {
280 self.source_slice.is_empty() && self.templated_slice.is_empty()
281 }
282}
283
284impl PartialEq for PositionMarker {
285 fn eq(&self, other: &Self) -> bool {
286 self.working_loc() == other.working_loc()
287 }
288}
289
290impl PartialOrd for PositionMarker {
291 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
292 Some(self.working_loc().cmp(&other.working_loc()))
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use std::ops::Range;
299
300 use crate::parser::markers::PositionMarker;
301 use crate::templaters::base::TemplatedFile;
302
303 #[test]
305 fn test_markers_infer_next_position() {
306 struct Test {
307 raw: String,
308 start: Range<usize>,
309 end: Range<usize>,
310 }
311
312 let tests: Vec<Test> = vec![
313 Test {
314 raw: "fsaljk".to_string(),
315 start: 0..0,
316 end: 0..6,
317 },
318 Test {
319 raw: "".to_string(),
320 start: 2..2,
321 end: 2..2,
322 },
323 Test {
324 raw: "\n".to_string(),
325 start: 2..2,
326 end: 3..1,
327 },
328 Test {
329 raw: "boo\n".to_string(),
330 start: 2..2,
331 end: 3..1,
332 },
333 Test {
334 raw: "boo\nfoo".to_string(),
335 start: 2..2,
336 end: 3..4,
337 },
338 Test {
339 raw: "\nfoo".to_string(),
340 start: 2..2,
341 end: 3..4,
342 },
343 ];
344
345 for t in tests {
346 assert_eq!(
347 (t.end.start, t.end.end),
348 PositionMarker::infer_next_position(&t.raw, t.start.start, t.start.end)
349 );
350 }
351 }
352
353 #[test]
355 fn test_markers_setting_position_raw() {
356 let template: TemplatedFile = "foobar".into();
357 assert_eq!(template.get_line_pos_of_char_pos(2, true), (1, 3));
359 assert_eq!(template.get_line_pos_of_char_pos(2, false), (1, 3));
360 let pos = PositionMarker::new(2..5, 2..5, template, None, None);
362 assert_eq!(pos.working_loc(), (1, 3));
364 }
365
366 #[test]
368 fn test_markers_setting_position_working() {
369 let templ: TemplatedFile = "foobar".into();
370 let pos = PositionMarker::new(2..5, 2..5, templ, Some(4), Some(4));
371 assert_eq!(pos.working_loc(), (4, 4))
373 }
374
375 #[test]
377 fn test_markers_comparison() {
378 let templ: TemplatedFile = "abc".into();
379
380 let a_pos = PositionMarker::new(0..1, 0..1, templ.clone(), None, None);
382 let b_pos = PositionMarker::new(1..2, 1..2, templ.clone(), None, None);
383 let c_pos = PositionMarker::new(2..3, 2..3, templ.clone(), None, None);
384
385 let all_pos = [&a_pos, &b_pos, &c_pos];
386
387 assert!(all_pos.iter().all(|p| p == p));
389
390 assert!(a_pos != b_pos && a_pos != c_pos && b_pos != c_pos);
392
393 assert!(a_pos < b_pos && b_pos < c_pos);
396 assert!(!(c_pos < a_pos));
397
398 assert!(c_pos > a_pos && c_pos > b_pos);
400 assert!(!(a_pos > c_pos));
401
402 assert!(all_pos.iter().all(|p| a_pos <= **p));
404
405 assert!(all_pos.iter().all(|p| c_pos >= **p));
407 }
408}