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 contents_of_span(&self, span: Span) -> Option<String> {
93 let source_file1 = self.find_source_file(span.lo)?;
94 let source_file2 = self.find_source_file(span.hi)?;
95 assert_eq!(source_file1.absolute_start, source_file2.absolute_start);
96 Some(source_file1.contents_of_span(span).to_string())
97 }
98}
99
100impl SourceMapInner {
101 fn try_allocate_address_space(&mut self, size: u32) -> Option<u32> {
103 let current = self.used_address_space;
104 self.used_address_space = current.checked_add(size)?.checked_add(1)?;
106 Some(current)
107 }
108}
109
110#[derive(Clone)]
115pub enum FileName {
116 Real(PathBuf),
118 Custom(String),
120}
121
122impl fmt::Display for FileName {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 match self {
125 Self::Real(x) if is_color() => x.display().fmt(f),
126 Self::Real(_) => Ok(()),
127 Self::Custom(x) => f.write_str(x),
128 }
129 }
130}
131
132pub fn is_color() -> bool {
134 std::env::var("NOCOLOR").unwrap_or_default().trim().is_empty()
135}
136
137pub struct SourceFile {
139 pub name: FileName,
141 pub src: String,
143 pub absolute_start: u32,
145 pub absolute_end: u32,
147}
148
149impl SourceFile {
150 fn new(name: FileName, src: String, absolute_start: u32) -> Self {
152 let absolute_end = absolute_start + src.len() as u32;
153 Self { name, src, absolute_start, absolute_end }
154 }
155
156 pub fn relative_offset(&self, absolute_offset: u32) -> u32 {
158 assert!(self.absolute_start <= absolute_offset);
159 assert!(absolute_offset <= self.absolute_end);
160 absolute_offset - self.absolute_start
161 }
162
163 pub fn contents_of_span(&self, span: Span) -> &str {
165 let start = self.relative_offset(span.lo);
166 let end = self.relative_offset(span.hi);
167 &self.src[start as usize..end as usize]
168 }
169
170 pub fn line_col(&self, absolute_offset: u32) -> (u32, u32) {
171 let relative_offset = self.relative_offset(absolute_offset);
172 let mut current_offset = 0u32;
173
174 for (i, line) in self.src.split('\n').enumerate() {
175 let end_of_line = current_offset + line.len() as u32;
176 if relative_offset <= end_of_line {
177 let chars = self.src[current_offset as usize..relative_offset as usize].chars().count();
178 return (i as u32, chars as u32);
179 }
180 current_offset = end_of_line + 1;
181 }
182
183 panic!("Can't happen.");
184 }
185
186 pub fn line_contents(&self, span: Span) -> LineContents<'_> {
187 let start = self.relative_offset(span.lo) as usize;
188 let end = self.relative_offset(span.hi) as usize;
189
190 let line_start = self.src[..=start].rfind('\n').map(|i| i + 1).unwrap_or(0);
191 let line_end = self.src[end..].find('\n').map(|x| x + end).unwrap_or(self.src.len());
192
193 LineContents {
194 line: self.src[..line_start].lines().count(),
195 contents: &self.src[line_start..line_end],
196 start: start.saturating_sub(line_start),
197 end: end.saturating_sub(line_start),
198 }
199 }
200}
201
202pub struct LineContents<'a> {
203 pub contents: &'a str,
204 pub line: usize,
205 pub start: usize,
206 pub end: usize,
207}
208
209impl fmt::Display for LineContents<'_> {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 const INDENT: &str = " ";
212
213 let mut current_underline = String::new();
214 let mut line = self.line;
215 let mut line_beginning = true;
216 let mut underline_started = false;
217
218 writeln!(f, "{INDENT} |")?;
219
220 for (i, c) in self.contents.chars().enumerate() {
221 if line_beginning {
222 write!(
223 f,
224 "{line:width$} | ",
225 line = line + 1,
227 width = INDENT.len()
228 )?;
229 }
230 if c == '\n' {
231 writeln!(f)?;
232 let underline = current_underline.trim_end();
234 if !underline.is_empty() {
235 writeln!(f, "{INDENT} | {underline}")?;
236 }
237 underline_started = false;
238 current_underline.clear();
239 line += 1;
240 line_beginning = true;
241 } else {
242 line_beginning = false;
243 if c != '\r' {
244 write!(f, "{c}")?;
245 if self.start <= i && i < self.end && (underline_started || !c.is_whitespace()) {
246 underline_started = true;
247 current_underline.push('^');
248 } else {
249 current_underline.push(' ');
250 }
251 }
252 }
253 }
254
255 let underline = current_underline.trim_end();
258 if !underline.is_empty() {
259 writeln!(f, "\n{INDENT} | {underline}")?;
260 }
261
262 Ok(())
263 }
264}
265
266pub struct LineCol {
268 pub source_file: Rc<SourceFile>,
270 pub line: u32,
272 pub col: u32,
274}