Skip to main content

huff_neo_utils/file/
span.rs

1use crate::file::file_source::FileSource;
2use serde::{Deserialize, Serialize};
3use std::ops::{Add, Range};
4use std::sync::Arc;
5
6/// A Span is a section of a source file.
7#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
8pub struct Span {
9    /// The start of the span.
10    pub start: usize,
11    /// The end of the span.
12    pub end: usize,
13    /// The Associated File
14    pub file: Option<Arc<FileSource>>,
15}
16
17impl Span {
18    /// An EOF spans [0, 0].
19    pub const EOF: Span = Span { start: 0, end: 0, file: None };
20
21    /// Public associated function to instantiate a new span.
22    pub fn new(Range { start, end }: Range<usize>, file: Option<Arc<FileSource>>) -> Self {
23        Self { start, end, file }
24    }
25
26    /// Converts a span to a range.
27    pub fn range(&self) -> Option<Range<usize>> {
28        (*self != Self::EOF).then_some(self.start..self.end)
29    }
30
31    /// Produces a file identifier string for errors
32    pub fn identifier(&self) -> String {
33        self.file.as_ref().map(|f| format!("\n-> {}", f.path)).unwrap_or_default()
34    }
35
36    /// Produces a source segment string
37    pub fn source_seg(&self) -> String {
38        self.file
39            .as_ref()
40            .map(|f| {
41                f.source
42                    .as_ref()
43                    .map(|s| {
44                        if self.start >= s.len() {
45                            // This should never happen, but currently does when the mapping from the flattened source is incorrect.
46                            return format!(
47                                "\nInternal compiler error: Start index out of range file={}, start={}, end={}, len={}.",
48                                self.file.clone().unwrap_or_default().path,
49                                self.start,
50                                self.end,
51                                s.len()
52                            );
53                        }
54                        let line_num = &s.as_bytes()[0..self.start].iter().filter(|&&c| c == b'\n').count() + 1;
55                        let line_start = &s[0..self.start].rfind('\n').unwrap_or(0);
56                        let line_end = self.end + s[self.end..s.len()].find('\n').unwrap_or(s.len() - self.end).to_owned();
57                        let padding = (0..line_num.to_string().len()).map(|_| " ").collect::<String>();
58                        format!(
59                            "\n     {}|\n  > {} | {}\n     {}|",
60                            padding,
61                            line_num,
62                            s[line_start.to_owned()..line_end].replace('\n', ""),
63                            padding
64                        )
65                    })
66                    .unwrap_or_default()
67            })
68            .unwrap_or_default()
69    }
70}
71
72impl From<Span> for Range<usize> {
73    fn from(span: Span) -> Self {
74        span.range().unwrap()
75    }
76}
77
78impl From<Range<usize>> for Span {
79    fn from(Range { start, end }: Range<usize>) -> Self {
80        Self { start, end, file: None }
81    }
82}
83
84impl Add for Span {
85    type Output = Span;
86
87    fn add(self, rhs: Span) -> Self::Output {
88        Span::new(self.start..rhs.end, None)
89    }
90}
91
92/// Spanned trait requires a type to have a span.
93pub trait Spanned {
94    /// Returns a Span.
95    fn span(&self) -> Span;
96}
97
98/// WithSpan associates a value to a Span.
99pub struct WithSpan<T> {
100    /// The value
101    pub value: T,
102    /// The associated Span
103    pub span: Span,
104}
105
106impl<T> WithSpan<T> {
107    /// Public associated function to instatiate a new WithSpan.
108    pub fn new(value: T, span: Span) -> Self {
109        Self { value, span }
110    }
111}
112
113impl<T> Spanned for WithSpan<T> {
114    fn span(&self) -> Span {
115        self.span.clone()
116    }
117}