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