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