1use crate::span::Span;
28
29use std::{
30 cell::RefCell,
31 fmt,
32 fs,
33 io,
34 path::{Path, PathBuf},
35 rc::Rc,
36};
37
38#[derive(Default)]
42pub struct SourceMap {
43 inner: RefCell<SourceMapInner>,
45}
46
47#[derive(Default)]
50struct SourceMapInner {
51 used_address_space: u32,
53
54 source_files: Vec<Rc<SourceFile>>,
59}
60
61impl SourceMap {
62 pub fn load_file(&self, path: &Path) -> io::Result<Rc<SourceFile>> {
64 Ok(self.new_source(&fs::read_to_string(path)?, FileName::Real(path.to_owned())))
65 }
66
67 pub fn new_source(&self, source: &str, name: FileName) -> Rc<SourceFile> {
69 let len = u32::try_from(source.len()).unwrap();
70 let mut inner = self.inner.borrow_mut();
71 let start_pos = inner.try_allocate_address_space(len).unwrap();
72 let source_file = Rc::new(SourceFile::new(name, source.to_owned(), start_pos));
73 inner.source_files.push(source_file.clone());
74 source_file
75 }
76
77 fn find_source_file_index(&self, pos: u32) -> Option<usize> {
79 self.inner
80 .borrow()
81 .source_files
82 .binary_search_by_key(&pos, |file| file.absolute_start)
83 .map_or_else(|p| p.checked_sub(1), Some)
84 }
85
86 pub fn find_source_file(&self, pos: u32) -> Option<Rc<SourceFile>> {
88 Some(self.inner.borrow().source_files[self.find_source_file_index(pos)?].clone())
89 }
90
91 pub fn source_file_by_filename(&self, filename: &FileName) -> Option<Rc<SourceFile>> {
92 self.inner.borrow().source_files.iter().find(|source_file| &source_file.name == filename).cloned()
94 }
95
96 pub fn contents_of_span(&self, span: Span) -> Option<String> {
98 let source_file1 = self.find_source_file(span.lo)?;
99 let source_file2 = self.find_source_file(span.hi)?;
100 assert_eq!(source_file1.absolute_start, source_file2.absolute_start);
101 Some(source_file1.contents_of_span(span).to_string())
102 }
103}
104
105impl SourceMapInner {
106 fn try_allocate_address_space(&mut self, size: u32) -> Option<u32> {
108 let current = self.used_address_space;
109 self.used_address_space = current.checked_add(size)?.checked_add(1)?;
111 Some(current)
112 }
113}
114
115#[derive(Clone, Eq, PartialEq, Hash)]
120pub enum FileName {
121 Real(PathBuf),
123 Custom(String),
125}
126
127impl fmt::Display for FileName {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 match self {
130 Self::Real(x) if is_color() => x.display().fmt(f),
131 Self::Real(_) => Ok(()),
132 Self::Custom(x) => f.write_str(x),
133 }
134 }
135}
136
137pub fn is_color() -> bool {
139 std::env::var("NOCOLOR").unwrap_or_default().trim().is_empty()
140}
141
142pub struct SourceFile {
144 pub name: FileName,
146 pub src: String,
148 pub absolute_start: u32,
150 pub absolute_end: u32,
152}
153
154impl SourceFile {
155 fn new(name: FileName, src: String, absolute_start: u32) -> Self {
157 let absolute_end = absolute_start + src.len() as u32;
158 Self { name, src, absolute_start, absolute_end }
159 }
160
161 pub fn relative_offset(&self, absolute_offset: u32) -> u32 {
163 assert!(self.absolute_start <= absolute_offset);
164 assert!(absolute_offset <= self.absolute_end);
165 absolute_offset - self.absolute_start
166 }
167
168 pub fn contents_of_span(&self, span: Span) -> &str {
170 let start = self.relative_offset(span.lo);
171 let end = self.relative_offset(span.hi);
172 &self.src[start as usize..end as usize]
173 }
174
175 pub fn line_col(&self, absolute_offset: u32) -> (u32, u32) {
176 let relative_offset = self.relative_offset(absolute_offset);
177 let mut current_offset = 0u32;
178
179 for (i, line) in self.src.split('\n').enumerate() {
180 let end_of_line = current_offset + line.len() as u32;
181 if relative_offset <= end_of_line {
182 let chars = self.src[current_offset as usize..relative_offset as usize].chars().count();
183 return (i as u32, chars as u32);
184 }
185 current_offset = end_of_line + 1;
186 }
187
188 panic!("Can't happen.");
189 }
190
191 pub fn line_contents(&self, span: Span) -> LineContents<'_> {
192 let start = self.relative_offset(span.lo) as usize;
193 let end = self.relative_offset(span.hi) as usize;
194
195 let line_start = self.src[..=start].rfind('\n').map(|i| i + 1).unwrap_or(0);
196 let line_end = self.src[end..].find('\n').map(|x| x + end).unwrap_or(self.src.len());
197
198 LineContents {
199 line: self.src[..line_start].lines().count(),
200 contents: &self.src[line_start..line_end],
201 start: start.saturating_sub(line_start),
202 end: end.saturating_sub(line_start),
203 }
204 }
205}
206
207pub struct LineContents<'a> {
208 pub contents: &'a str,
209 pub line: usize,
210 pub start: usize,
211 pub end: usize,
212}
213
214impl fmt::Display for LineContents<'_> {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 const INDENT: &str = " ";
217
218 let mut current_underline = String::new();
219 let mut line = self.line;
220 let mut line_beginning = true;
221 let mut underline_started = false;
222
223 writeln!(f, "{INDENT} |")?;
224
225 for (i, c) in self.contents.chars().enumerate() {
226 if line_beginning {
227 write!(
228 f,
229 "{line:width$} | ",
230 line = line + 1,
232 width = INDENT.len()
233 )?;
234 }
235 if c == '\n' {
236 writeln!(f)?;
237 let underline = current_underline.trim_end();
239 if !underline.is_empty() {
240 writeln!(f, "{INDENT} | {underline}")?;
241 }
242 underline_started = false;
243 current_underline.clear();
244 line += 1;
245 line_beginning = true;
246 } else {
247 line_beginning = false;
248 if c != '\r' {
249 write!(f, "{c}")?;
250 if self.start <= i && i < self.end && (underline_started || !c.is_whitespace()) {
251 underline_started = true;
252 current_underline.push('^');
253 } else {
254 current_underline.push(' ');
255 }
256 }
257 }
258 }
259
260 let underline = current_underline.trim_end();
263 if !underline.is_empty() {
264 writeln!(f, "\n{INDENT} | {underline}")?;
265 }
266
267 Ok(())
268 }
269}
270
271pub struct LineCol {
273 pub source_file: Rc<SourceFile>,
275 pub line: u32,
277 pub col: u32,
279}