1use std::fmt;
14
15#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct FileIdChirho(u32);
25
26impl FileIdChirho {
27 pub const SYNTHETIC_CHIRHO: Self = Self(u32::MAX);
30
31 #[inline]
33 pub fn as_raw_chirho(self) -> u32 {
34 self.0
35 }
36}
37
38impl fmt::Debug for FileIdChirho {
39 fn fmt(&self, f_chirho: &mut fmt::Formatter<'_>) -> fmt::Result {
40 if *self == Self::SYNTHETIC_CHIRHO {
41 write!(f_chirho, "FileIdChirho(SYNTHETIC)")
42 } else {
43 write!(f_chirho, "FileIdChirho({})", self.0)
44 }
45 }
46}
47
48#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
54pub struct ByteOffsetChirho(u32);
55
56impl ByteOffsetChirho {
57 #[inline]
59 pub const fn new_chirho(raw_chirho: u32) -> Self {
60 Self(raw_chirho)
61 }
62
63 #[inline]
65 pub fn from_usize_chirho(value_chirho: usize) -> Self {
66 Self(u32::try_from(value_chirho).unwrap_or(u32::MAX))
67 }
68
69 #[inline]
71 pub const fn as_u32_chirho(self) -> u32 {
72 self.0
73 }
74
75 #[inline]
77 pub const fn as_usize_chirho(self) -> usize {
78 self.0 as usize
79 }
80}
81
82impl fmt::Debug for ByteOffsetChirho {
83 fn fmt(&self, f_chirho: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f_chirho, "ByteOffset({})", self.0)
85 }
86}
87
88impl fmt::Display for ByteOffsetChirho {
89 fn fmt(&self, f_chirho: &mut fmt::Formatter<'_>) -> fmt::Result {
90 write!(f_chirho, "{}", self.0)
91 }
92}
93
94#[derive(Clone, Copy, PartialEq, Eq, Hash)]
105pub struct SpanChirho {
106 file_id_chirho: FileIdChirho,
107 start_chirho: ByteOffsetChirho,
108 end_chirho: ByteOffsetChirho,
109}
110
111impl SpanChirho {
112 #[inline]
114 pub fn new_chirho(
115 file_id_chirho: FileIdChirho,
116 start_chirho: ByteOffsetChirho,
117 end_chirho: ByteOffsetChirho,
118 ) -> Self {
119 debug_assert!(
120 start_chirho <= end_chirho,
121 "SpanChirho::new_chirho requires start <= end"
122 );
123 Self {
124 file_id_chirho,
125 start_chirho,
126 end_chirho,
127 }
128 }
129
130 pub const DUMMY_CHIRHO: Self = Self {
132 file_id_chirho: FileIdChirho::SYNTHETIC_CHIRHO,
133 start_chirho: ByteOffsetChirho::new_chirho(0),
134 end_chirho: ByteOffsetChirho::new_chirho(0),
135 };
136
137 #[inline]
139 pub const fn file_id_chirho(self) -> FileIdChirho {
140 self.file_id_chirho
141 }
142
143 #[inline]
145 pub const fn start_chirho(self) -> ByteOffsetChirho {
146 self.start_chirho
147 }
148
149 #[inline]
151 pub const fn end_chirho(self) -> ByteOffsetChirho {
152 self.end_chirho
153 }
154
155 #[inline]
157 pub const fn is_valid_chirho(self) -> bool {
158 self.start_chirho.0 <= self.end_chirho.0
159 }
160
161 #[inline]
163 pub const fn len_chirho(self) -> u32 {
164 self.end_chirho.0.saturating_sub(self.start_chirho.0)
165 }
166
167 #[inline]
169 pub const fn is_empty_chirho(self) -> bool {
170 self.start_chirho.0 == self.end_chirho.0
171 }
172
173 #[inline]
175 pub fn contains_chirho(self, other_chirho: SpanChirho) -> bool {
176 self.file_id_chirho == other_chirho.file_id_chirho
177 && self.start_chirho <= other_chirho.start_chirho
178 && self.end_chirho >= other_chirho.end_chirho
179 }
180
181 #[inline]
184 pub fn merge_chirho(self, other_chirho: SpanChirho) -> Option<SpanChirho> {
185 if self.file_id_chirho != other_chirho.file_id_chirho {
186 return None;
187 }
188 let start_chirho = if self.start_chirho < other_chirho.start_chirho {
189 self.start_chirho
190 } else {
191 other_chirho.start_chirho
192 };
193 let end_chirho = if self.end_chirho > other_chirho.end_chirho {
194 self.end_chirho
195 } else {
196 other_chirho.end_chirho
197 };
198 Some(SpanChirho::new_chirho(
199 self.file_id_chirho,
200 start_chirho,
201 end_chirho,
202 ))
203 }
204
205 #[inline]
208 pub const fn subspan_chirho(
209 self,
210 relative_start_chirho: u32,
211 relative_end_chirho: u32,
212 ) -> SpanChirho {
213 SpanChirho {
214 file_id_chirho: self.file_id_chirho,
215 start_chirho: ByteOffsetChirho::new_chirho(self.start_chirho.0 + relative_start_chirho),
216 end_chirho: ByteOffsetChirho::new_chirho(self.start_chirho.0 + relative_end_chirho),
217 }
218 }
219
220 #[inline]
223 pub fn clamp_to_source_chirho(self, source_len_chirho: usize) -> SpanChirho {
224 let source_end_chirho = ByteOffsetChirho::from_usize_chirho(source_len_chirho);
225 let clamped_end_chirho = if self.end_chirho < source_end_chirho {
226 self.end_chirho
227 } else {
228 source_end_chirho
229 };
230 let clamped_end_chirho = if clamped_end_chirho < self.start_chirho {
231 self.start_chirho
232 } else {
233 clamped_end_chirho
234 };
235 SpanChirho::new_chirho(self.file_id_chirho, self.start_chirho, clamped_end_chirho)
236 }
237
238 #[inline]
241 pub fn text_chirho<'a>(&self, source_chirho: &'a str) -> Option<&'a str> {
242 let start_chirho = self.start_chirho.as_usize_chirho();
243 let end_chirho = self.end_chirho.as_usize_chirho();
244 source_chirho.get(start_chirho..end_chirho)
245 }
246}
247
248impl fmt::Debug for SpanChirho {
249 fn fmt(&self, f_chirho: &mut fmt::Formatter<'_>) -> fmt::Result {
250 write!(
251 f_chirho,
252 "Span({:?}, {}..{})",
253 self.file_id_chirho, self.start_chirho.0, self.end_chirho.0
254 )
255 }
256}
257
258impl fmt::Display for SpanChirho {
259 fn fmt(&self, f_chirho: &mut fmt::Formatter<'_>) -> fmt::Result {
260 write!(f_chirho, "{}..{}", self.start_chirho.0, self.end_chirho.0)
261 }
262}
263
264#[derive(Debug, Clone)]
271pub struct LineIndexChirho {
272 line_starts_chirho: Vec<ByteOffsetChirho>,
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub struct LineColChirho {
279 pub line_chirho: u32,
281 pub col_chirho: u32,
283}
284
285impl LineIndexChirho {
286 pub fn new_chirho(source_chirho: &str) -> Self {
288 let mut line_starts_chirho = vec![ByteOffsetChirho::new_chirho(0)];
289 for (byte_index_chirho, byte_chirho) in source_chirho.bytes().enumerate() {
290 if byte_chirho == b'\n' {
291 line_starts_chirho.push(ByteOffsetChirho::from_usize_chirho(byte_index_chirho + 1));
292 }
293 }
294 Self { line_starts_chirho }
295 }
296
297 #[inline]
299 pub fn line_count_chirho(&self) -> usize {
300 self.line_starts_chirho.len()
301 }
302
303 pub fn line_col_chirho(&self, offset_chirho: ByteOffsetChirho) -> LineColChirho {
305 let line_index_chirho = self
306 .line_starts_chirho
307 .partition_point(|&start_chirho| start_chirho <= offset_chirho)
308 .saturating_sub(1);
309 let line_start_chirho = self.line_starts_chirho[line_index_chirho];
310 LineColChirho {
311 line_chirho: (line_index_chirho as u32) + 1,
312 col_chirho: offset_chirho
313 .as_u32_chirho()
314 .saturating_sub(line_start_chirho.as_u32_chirho())
315 + 1,
316 }
317 }
318
319 pub fn line_start_chirho(&self, line_number_chirho: u32) -> Option<ByteOffsetChirho> {
322 let index_chirho = line_number_chirho.checked_sub(1)? as usize;
323 self.line_starts_chirho.get(index_chirho).copied()
324 }
325}
326
327#[derive(Debug, Default)]
335pub struct SourceMapChirho {
336 files_chirho: Vec<SourceEntryChirho>,
337}
338
339#[derive(Debug)]
341struct SourceEntryChirho {
342 name_chirho: String,
343 source_chirho: String,
344 line_index_chirho: LineIndexChirho,
345}
346
347impl SourceMapChirho {
348 pub fn new_chirho() -> Self {
350 Self::default()
351 }
352
353 pub fn add_file_chirho(
355 &mut self,
356 name_chirho: impl Into<String>,
357 source_chirho: impl Into<String>,
358 ) -> FileIdChirho {
359 let source_str_chirho = source_chirho.into();
360 let line_index_chirho = LineIndexChirho::new_chirho(&source_str_chirho);
361 let id_chirho = FileIdChirho(self.files_chirho.len() as u32);
362 self.files_chirho.push(SourceEntryChirho {
363 name_chirho: name_chirho.into(),
364 source_chirho: source_str_chirho,
365 line_index_chirho,
366 });
367 id_chirho
368 }
369
370 pub fn file_name_chirho(&self, id_chirho: FileIdChirho) -> Option<&str> {
372 self.files_chirho
373 .get(id_chirho.0 as usize)
374 .map(|entry_chirho| entry_chirho.name_chirho.as_str())
375 }
376
377 pub fn file_source_chirho(&self, id_chirho: FileIdChirho) -> Option<&str> {
379 self.files_chirho
380 .get(id_chirho.0 as usize)
381 .map(|entry_chirho| entry_chirho.source_chirho.as_str())
382 }
383
384 pub fn line_index_chirho(&self, id_chirho: FileIdChirho) -> Option<&LineIndexChirho> {
386 self.files_chirho
387 .get(id_chirho.0 as usize)
388 .map(|entry_chirho| &entry_chirho.line_index_chirho)
389 }
390
391 pub fn resolve_span_start_chirho(
393 &self,
394 span_chirho: SpanChirho,
395 ) -> Option<(&str, LineColChirho)> {
396 let entry_chirho = self
397 .files_chirho
398 .get(span_chirho.file_id_chirho.0 as usize)?;
399 let line_col_chirho = entry_chirho
400 .line_index_chirho
401 .line_col_chirho(span_chirho.start_chirho);
402 Some((&entry_chirho.name_chirho, line_col_chirho))
403 }
404
405 pub fn span_text_chirho(&self, span_chirho: SpanChirho) -> Option<&str> {
407 let source_chirho = self.file_source_chirho(span_chirho.file_id_chirho)?;
408 span_chirho.text_chirho(source_chirho)
409 }
410
411 #[inline]
413 pub fn file_count_chirho(&self) -> usize {
414 self.files_chirho.len()
415 }
416}
417
418#[derive(Clone, Copy, PartialEq, Eq, Hash)]
427pub struct SpannedChirho<T> {
428 pub node_chirho: T,
430 pub span_chirho: SpanChirho,
432}
433
434impl<T> SpannedChirho<T> {
435 #[inline]
437 pub const fn new_chirho(node_chirho: T, span_chirho: SpanChirho) -> Self {
438 Self {
439 node_chirho,
440 span_chirho,
441 }
442 }
443
444 #[inline]
446 pub fn map_chirho<U>(self, f_chirho: impl FnOnce(T) -> U) -> SpannedChirho<U> {
447 SpannedChirho {
448 node_chirho: f_chirho(self.node_chirho),
449 span_chirho: self.span_chirho,
450 }
451 }
452}
453
454impl<T: fmt::Debug> fmt::Debug for SpannedChirho<T> {
455 fn fmt(&self, f_chirho: &mut fmt::Formatter<'_>) -> fmt::Result {
456 write!(f_chirho, "{:?} @ {:?}", self.node_chirho, self.span_chirho)
457 }
458}
459
460#[cfg(test)]
465mod tests_chirho {
466 use super::*;
467
468 #[test]
469 fn file_id_debug_chirho() {
470 let id_chirho = FileIdChirho(0);
471 assert_eq!(format!("{id_chirho:?}"), "FileIdChirho(0)");
472 assert_eq!(
473 format!("{:?}", FileIdChirho::SYNTHETIC_CHIRHO),
474 "FileIdChirho(SYNTHETIC)"
475 );
476 }
477
478 #[test]
479 fn span_basics_chirho() {
480 let file_chirho = FileIdChirho(0);
481 let span_chirho = SpanChirho::new_chirho(
482 file_chirho,
483 ByteOffsetChirho::new_chirho(5),
484 ByteOffsetChirho::new_chirho(10),
485 );
486 assert_eq!(span_chirho.len_chirho(), 5);
487 assert!(!span_chirho.is_empty_chirho());
488 assert!(span_chirho.is_valid_chirho());
489 assert_eq!(span_chirho.file_id_chirho(), file_chirho);
490 }
491
492 #[test]
493 fn span_empty_chirho() {
494 let span_chirho = SpanChirho::DUMMY_CHIRHO;
495 assert!(span_chirho.is_empty_chirho());
496 assert_eq!(span_chirho.len_chirho(), 0);
497 }
498
499 #[test]
500 fn span_merge_same_file_chirho() {
501 let file_chirho = FileIdChirho(0);
502 let a_chirho = SpanChirho::new_chirho(
503 file_chirho,
504 ByteOffsetChirho::new_chirho(2),
505 ByteOffsetChirho::new_chirho(5),
506 );
507 let b_chirho = SpanChirho::new_chirho(
508 file_chirho,
509 ByteOffsetChirho::new_chirho(8),
510 ByteOffsetChirho::new_chirho(12),
511 );
512 let merged_chirho = a_chirho.merge_chirho(b_chirho).unwrap();
513 assert_eq!(
514 merged_chirho.start_chirho(),
515 ByteOffsetChirho::new_chirho(2)
516 );
517 assert_eq!(merged_chirho.end_chirho(), ByteOffsetChirho::new_chirho(12));
518 }
519
520 #[test]
521 fn span_merge_different_files_chirho() {
522 let a_chirho = SpanChirho::new_chirho(
523 FileIdChirho(0),
524 ByteOffsetChirho::new_chirho(0),
525 ByteOffsetChirho::new_chirho(5),
526 );
527 let b_chirho = SpanChirho::new_chirho(
528 FileIdChirho(1),
529 ByteOffsetChirho::new_chirho(0),
530 ByteOffsetChirho::new_chirho(5),
531 );
532 assert!(a_chirho.merge_chirho(b_chirho).is_none());
533 }
534
535 #[test]
536 fn span_contains_chirho() {
537 let file_chirho = FileIdChirho(0);
538 let outer_chirho = SpanChirho::new_chirho(
539 file_chirho,
540 ByteOffsetChirho::new_chirho(0),
541 ByteOffsetChirho::new_chirho(20),
542 );
543 let inner_chirho = SpanChirho::new_chirho(
544 file_chirho,
545 ByteOffsetChirho::new_chirho(5),
546 ByteOffsetChirho::new_chirho(15),
547 );
548 assert!(outer_chirho.contains_chirho(inner_chirho));
549 assert!(!inner_chirho.contains_chirho(outer_chirho));
550 }
551
552 #[test]
553 fn span_text_extraction_chirho() {
554 let source_chirho = "module Main where";
555 let file_chirho = FileIdChirho(0);
556 let span_chirho = SpanChirho::new_chirho(
557 file_chirho,
558 ByteOffsetChirho::new_chirho(7),
559 ByteOffsetChirho::new_chirho(11),
560 );
561 assert_eq!(span_chirho.text_chirho(source_chirho), Some("Main"));
562 }
563
564 #[test]
565 fn subspan_chirho() {
566 let file_chirho = FileIdChirho(0);
567 let span_chirho = SpanChirho::new_chirho(
568 file_chirho,
569 ByteOffsetChirho::new_chirho(10),
570 ByteOffsetChirho::new_chirho(20),
571 );
572 let sub_chirho = span_chirho.subspan_chirho(2, 5);
573 assert_eq!(sub_chirho.start_chirho(), ByteOffsetChirho::new_chirho(12));
574 assert_eq!(sub_chirho.end_chirho(), ByteOffsetChirho::new_chirho(15));
575 }
576
577 #[test]
578 fn span_clamp_to_source_chirho() {
579 let span_chirho = SpanChirho::new_chirho(
580 FileIdChirho(0),
581 ByteOffsetChirho::new_chirho(3),
582 ByteOffsetChirho::new_chirho(12),
583 );
584 let clamped_span_chirho = span_chirho.clamp_to_source_chirho(8);
585 assert!(clamped_span_chirho.is_valid_chirho());
586 assert_eq!(
587 clamped_span_chirho.end_chirho(),
588 ByteOffsetChirho::new_chirho(8)
589 );
590 }
591
592 #[test]
593 fn merge_contains_inputs_property_chirho() {
594 let file_chirho = FileIdChirho(0);
595 for start_a_raw_chirho in 0_u32..=8 {
596 for end_a_raw_chirho in start_a_raw_chirho..=8 {
597 let span_a_chirho = SpanChirho::new_chirho(
598 file_chirho,
599 ByteOffsetChirho::new_chirho(start_a_raw_chirho),
600 ByteOffsetChirho::new_chirho(end_a_raw_chirho),
601 );
602
603 for start_b_raw_chirho in 0_u32..=8 {
604 for end_b_raw_chirho in start_b_raw_chirho..=8 {
605 let span_b_chirho = SpanChirho::new_chirho(
606 file_chirho,
607 ByteOffsetChirho::new_chirho(start_b_raw_chirho),
608 ByteOffsetChirho::new_chirho(end_b_raw_chirho),
609 );
610 let merged_span_chirho = span_a_chirho.merge_chirho(span_b_chirho).unwrap();
611 assert!(merged_span_chirho.contains_chirho(span_a_chirho));
612 assert!(merged_span_chirho.contains_chirho(span_b_chirho));
613 }
614 }
615 }
616 }
617 }
618
619 #[test]
620 fn subspan_stays_within_parent_property_chirho() {
621 let parent_span_chirho = SpanChirho::new_chirho(
622 FileIdChirho(0),
623 ByteOffsetChirho::new_chirho(10),
624 ByteOffsetChirho::new_chirho(20),
625 );
626 let parent_len_chirho = parent_span_chirho.len_chirho();
627
628 for relative_start_chirho in 0_u32..=parent_len_chirho {
629 for relative_end_chirho in relative_start_chirho..=parent_len_chirho {
630 let subspan_chirho =
631 parent_span_chirho.subspan_chirho(relative_start_chirho, relative_end_chirho);
632 assert!(parent_span_chirho.contains_chirho(subspan_chirho));
633 }
634 }
635 }
636
637 #[test]
638 fn text_returns_none_for_out_of_bounds_spans_property_chirho() {
639 let source_chirho = "module Main where";
640 let file_chirho = FileIdChirho(0);
641 let source_len_chirho = source_chirho.len() as u32;
642
643 for start_raw_chirho in 0_u32..=source_len_chirho + 2 {
644 for extra_len_chirho in 1_u32..=3 {
645 let end_raw_chirho = start_raw_chirho + extra_len_chirho;
646 if end_raw_chirho <= source_len_chirho {
647 continue;
648 }
649
650 let span_chirho = SpanChirho::new_chirho(
651 file_chirho,
652 ByteOffsetChirho::new_chirho(start_raw_chirho),
653 ByteOffsetChirho::new_chirho(end_raw_chirho),
654 );
655 assert_eq!(span_chirho.text_chirho(source_chirho), None);
656 }
657 }
658 }
659
660 #[test]
661 fn line_index_single_line_chirho() {
662 let index_chirho = LineIndexChirho::new_chirho("hello world");
663 assert_eq!(index_chirho.line_count_chirho(), 1);
664 let lc_chirho = index_chirho.line_col_chirho(ByteOffsetChirho::new_chirho(6));
665 assert_eq!(lc_chirho.line_chirho, 1);
666 assert_eq!(lc_chirho.col_chirho, 7);
667 }
668
669 #[test]
670 fn line_index_multi_line_chirho() {
671 let source_chirho = "line one\nline two\nline three\n";
672 let index_chirho = LineIndexChirho::new_chirho(source_chirho);
673 let line_count_chirho = index_chirho.line_count_chirho();
679 assert!(
680 line_count_chirho == 4,
681 "expected 4 line starts, got {line_count_chirho}"
682 );
683
684 let lc_chirho = index_chirho.line_col_chirho(ByteOffsetChirho::new_chirho(9));
686 assert_eq!(lc_chirho.line_chirho, 2);
687 assert_eq!(lc_chirho.col_chirho, 1);
688
689 let lc2_chirho = index_chirho.line_col_chirho(ByteOffsetChirho::new_chirho(25));
691 assert_eq!(lc2_chirho.line_chirho, 3);
692 assert_eq!(lc2_chirho.col_chirho, 8); }
694
695 #[test]
696 fn source_map_basics_chirho() {
697 let mut map_chirho = SourceMapChirho::new_chirho();
698 let id_chirho =
699 map_chirho.add_file_chirho("Main.hs", "module Main where\nmain = putStrLn \"hi\"\n");
700
701 assert_eq!(map_chirho.file_count_chirho(), 1);
702 assert_eq!(map_chirho.file_name_chirho(id_chirho), Some("Main.hs"));
703
704 let span_chirho = SpanChirho::new_chirho(
705 id_chirho,
706 ByteOffsetChirho::new_chirho(7),
707 ByteOffsetChirho::new_chirho(11),
708 );
709 assert_eq!(map_chirho.span_text_chirho(span_chirho), Some("Main"));
710
711 let (name_chirho, lc_chirho) = map_chirho.resolve_span_start_chirho(span_chirho).unwrap();
712 assert_eq!(name_chirho, "Main.hs");
713 assert_eq!(lc_chirho.line_chirho, 1);
714 assert_eq!(lc_chirho.col_chirho, 8);
715 }
716
717 #[test]
718 fn spanned_map_chirho() {
719 let span_chirho = SpanChirho::DUMMY_CHIRHO;
720 let spanned_chirho = SpannedChirho::new_chirho(42_i32, span_chirho);
721 let mapped_chirho = spanned_chirho.map_chirho(|n_chirho| n_chirho.to_string());
722 assert_eq!(mapped_chirho.node_chirho, "42");
723 assert_eq!(mapped_chirho.span_chirho, span_chirho);
724 }
725}