1use std::fmt;
31use std::sync::{Arc, OnceLock};
32
33use crate::{BytePos, Span};
34
35#[derive(Clone)]
45pub struct SourceBytes(Arc<dyn AsRef<[u8]> + Send + Sync>);
46
47impl SourceBytes {
48 pub fn new(bytes: impl AsRef<[u8]> + Send + Sync + 'static) -> SourceBytes {
50 SourceBytes(Arc::new(bytes))
51 }
52
53 #[inline]
55 pub fn as_slice(&self) -> &[u8] {
56 (*self.0).as_ref()
57 }
58}
59
60impl fmt::Debug for SourceBytes {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "SourceBytes({} bytes)", self.as_slice().len())
63 }
64}
65
66impl AsRef<[u8]> for SourceBytes {
67 #[inline]
68 fn as_ref(&self) -> &[u8] {
69 self.as_slice()
70 }
71}
72
73impl std::ops::Deref for SourceBytes {
74 type Target = [u8];
75
76 #[inline]
77 fn deref(&self) -> &[u8] {
78 self.as_slice()
79 }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
88pub struct FileId(u32);
89
90impl FileId {
91 #[inline]
93 pub const fn index(self) -> usize {
94 self.0 as usize
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct Loc {
105 pub file: FileId,
107 pub line: u32,
109 pub column: u32,
116}
117
118pub struct SourceFile {
126 pub id: FileId,
128 pub name: String,
131 pub start: BytePos,
133 pub end: BytePos,
135 pub included_from: Option<Span>,
138 bytes: SourceBytes,
139 lines: OnceLock<Vec<BytePos>>,
142}
143
144impl fmt::Debug for SourceFile {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 f.debug_struct("SourceFile")
149 .field("id", &self.id)
150 .field("name", &self.name)
151 .field("start", &self.start)
152 .field("end", &self.end)
153 .field("included_from", &self.included_from)
154 .finish()
155 }
156}
157
158impl SourceFile {
159 #[inline]
161 pub fn bytes(&self) -> &[u8] {
162 self.bytes.as_slice()
163 }
164
165 #[inline]
167 pub fn shared_bytes(&self) -> SourceBytes {
168 self.bytes.clone()
169 }
170
171 #[inline]
173 pub fn len(&self) -> u32 {
174 self.end - self.start
175 }
176
177 #[inline]
179 pub fn is_empty(&self) -> bool {
180 self.start == self.end
181 }
182
183 #[inline]
188 pub fn contains(&self, pos: BytePos) -> bool {
189 self.start <= pos && pos <= self.end
190 }
191
192 pub fn line_count(&self) -> u32 {
195 u32::try_from(self.lines().len()).unwrap_or(u32::MAX)
196 }
197
198 pub fn line_bytes(&self, line: u32) -> Option<&[u8]> {
202 let lines = self.lines();
203 let index = usize::try_from(line.checked_sub(1)?).ok()?;
204 let from = *lines.get(index)? - self.start;
205 let to = lines.get(index + 1).map_or(self.len(), |next| *next - self.start);
206 let text = self.bytes().get(from as usize..to as usize)?;
207 let text = text.strip_suffix(b"\n").unwrap_or(text);
210 Some(text.strip_suffix(b"\r").unwrap_or(text))
211 }
212
213 pub fn position(&self, pos: BytePos) -> Option<Loc> {
215 let (line, begin) = self.line_of(pos)?;
216 Some(Loc { file: self.id, line, column: pos - begin + 1 })
217 }
218
219 pub fn line_span(&self, pos: BytePos) -> Option<Span> {
221 let (line, begin) = self.line_of(pos)?;
222 let end = self.lines().get(line as usize).copied().unwrap_or(self.end);
223 Some(Span::new(begin, end))
224 }
225
226 fn line_of(&self, pos: BytePos) -> Option<(u32, BytePos)> {
228 if !self.contains(pos) {
229 return None;
230 }
231 let lines = self.lines();
232 let line = lines.partition_point(|&start| start <= pos);
235 let begin = lines.get(line.saturating_sub(1)).copied().unwrap_or(self.start);
236 Some((u32::try_from(line).unwrap_or(u32::MAX), begin))
237 }
238
239 fn lines(&self) -> &[BytePos] {
241 self.lines.get_or_init(|| {
242 let bytes = self.bytes();
243 let mut starts = Vec::with_capacity(bytes.len() / 24 + 1);
246 starts.push(self.start);
247 for (at, _) in bytes.iter().enumerate().filter(|&(_, &b)| b == b'\n') {
248 let next = self.start + u32::try_from(at).unwrap_or(u32::MAX - 1) + 1;
249 if next < self.end {
253 starts.push(next);
254 }
255 }
256 starts
257 })
258 }
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub struct SourceMapFull;
268
269impl fmt::Display for SourceMapFull {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 f.write_str("the translation unit does not fit in the four gigabyte source map")
272 }
273}
274
275impl std::error::Error for SourceMapFull {}
276
277#[derive(Debug, Default)]
279pub struct SourceMap {
280 files: Vec<SourceFile>,
281 next: BytePos,
282}
283
284impl SourceMap {
285 pub fn new() -> SourceMap {
287 SourceMap::default()
288 }
289
290 pub fn add(
296 &mut self,
297 name: impl Into<String>,
298 bytes: impl AsRef<[u8]> + Send + Sync + 'static,
299 ) -> Result<FileId, SourceMapFull> {
300 self.push(name.into(), SourceBytes::new(bytes), None)
301 }
302
303 pub fn add_shared(
312 &mut self,
313 name: impl Into<String>,
314 bytes: SourceBytes,
315 included_from: Option<Span>,
316 ) -> Result<FileId, SourceMapFull> {
317 self.push(name.into(), bytes, included_from)
318 }
319
320 pub fn add_included(
326 &mut self,
327 name: impl Into<String>,
328 bytes: impl AsRef<[u8]> + Send + Sync + 'static,
329 from: Span,
330 ) -> Result<FileId, SourceMapFull> {
331 self.push(name.into(), SourceBytes::new(bytes), Some(from))
332 }
333
334 fn push(
335 &mut self,
336 name: String,
337 bytes: SourceBytes,
338 included_from: Option<Span>,
339 ) -> Result<FileId, SourceMapFull> {
340 let len = u32::try_from(bytes.as_slice().len()).map_err(|_| SourceMapFull)?;
341 let start = self.next;
342 let end = start.checked_add(len).ok_or(SourceMapFull)?;
343 self.next = end.checked_add(1).filter(|&n| n < BytePos::MAX).ok_or(SourceMapFull)?;
348 let id = FileId(u32::try_from(self.files.len()).map_err(|_| SourceMapFull)?);
349 self.files.push(SourceFile {
350 id,
351 name,
352 start,
353 end,
354 included_from,
355 bytes,
356 lines: OnceLock::new(),
357 });
358 Ok(id)
359 }
360
361 pub fn files(&self) -> &[SourceFile] {
363 &self.files
364 }
365
366 pub fn file(&self, id: FileId) -> &SourceFile {
373 &self.files[id.index()]
374 }
375
376 pub fn lookup_file(&self, pos: BytePos) -> Option<FileId> {
378 if pos == BytePos::MAX {
379 return None;
380 }
381 let at = self.files.partition_point(|f| f.start <= pos);
385 let file = self.files.get(at.checked_sub(1)?)?;
386 file.contains(pos).then_some(file.id)
387 }
388
389 pub fn lookup(&self, pos: BytePos) -> Option<Loc> {
391 self.file(self.lookup_file(pos)?).position(pos)
392 }
393
394 pub fn render_position(&self, pos: BytePos) -> String {
399 match self.lookup(pos) {
400 Some(loc) => format!("{}:{}:{}", self.file(loc.file).name, loc.line, loc.column),
401 None => "<unknown>".to_owned(),
402 }
403 }
404
405 pub fn include_stack(&self, pos: BytePos) -> Vec<Span> {
412 let mut stack = Vec::new();
413 let mut at = self.lookup_file(pos);
414 while let Some(file) = at {
415 let Some(from) = self.file(file).included_from else { break };
416 stack.push(from);
417 at = self.lookup_file(from.lo);
418 if stack.len() > self.files.len() {
422 break;
423 }
424 }
425 stack
426 }
427
428 pub fn used(&self) -> BytePos {
430 self.next
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 fn map_with(files: &[(&str, &str)]) -> (SourceMap, Vec<FileId>) {
439 let mut map = SourceMap::new();
440 let ids = files
441 .iter()
442 .map(|(name, text)| map.add(*name, text.as_bytes().to_vec()).unwrap())
443 .collect();
444 (map, ids)
445 }
446
447 #[test]
448 fn the_first_file_starts_at_zero_and_the_next_one_after_a_gap() {
449 let (map, ids) = map_with(&[("a.c", "ab"), ("b.c", "cd")]);
450 assert_eq!(map.file(ids[0]).start, 0);
451 assert_eq!(map.file(ids[0]).end, 2);
452 assert_eq!(map.file(ids[1]).start, 3);
453 assert_eq!(map.used(), 6);
454 }
455
456 #[test]
457 fn the_position_after_a_file_belongs_to_that_file_and_not_the_next() {
458 let (map, ids) = map_with(&[("a.c", "ab"), ("b.c", "cd")]);
459 assert_eq!(map.lookup_file(2), Some(ids[0]));
460 assert_eq!(map.lookup_file(3), Some(ids[1]));
461 }
462
463 #[test]
464 fn a_position_in_the_gap_is_in_no_file() {
465 let mut map = SourceMap::new();
466 map.add("a.c", b"ab".to_vec()).unwrap();
467 assert_eq!(map.lookup_file(3), None);
470 assert_eq!(map.render_position(3), "<unknown>");
471 }
472
473 #[test]
474 fn a_dummy_span_resolves_to_nothing() {
475 let (map, _) = map_with(&[("a.c", "ab")]);
476 assert_eq!(map.lookup(Span::DUMMY.lo), None);
477 assert_eq!(map.lookup_file(BytePos::MAX), None);
478 }
479
480 #[test]
481 fn lines_and_columns_count_from_one() {
482 let (map, ids) = map_with(&[("a.c", "one\ntwo\nthree\n")]);
483 let start = map.file(ids[0]).start;
484 assert_eq!(map.lookup(start).unwrap(), Loc { file: ids[0], line: 1, column: 1 });
485 assert_eq!(map.lookup(start + 4).unwrap(), Loc { file: ids[0], line: 2, column: 1 });
486 assert_eq!(map.lookup(start + 6).unwrap(), Loc { file: ids[0], line: 2, column: 3 });
487 assert_eq!(map.render_position(start + 8), "a.c:3:1");
488 }
489
490 #[test]
491 fn a_trailing_newline_does_not_open_a_line() {
492 let (map, ids) = map_with(&[("a.c", "one\ntwo\n"), ("b.c", "one\ntwo")]);
493 assert_eq!(map.file(ids[0]).line_count(), 2);
494 assert_eq!(map.file(ids[1]).line_count(), 2);
495 }
496
497 #[test]
498 fn a_blank_line_is_a_line() {
499 let (map, ids) = map_with(&[("a.c", "one\n\nthree\n")]);
500 let file = map.file(ids[0]);
501 assert_eq!(file.line_count(), 3);
502 assert_eq!(file.line_bytes(2), Some(&b""[..]));
503 assert_eq!(file.line_bytes(3), Some(&b"three"[..]));
504 assert_eq!(file.line_bytes(4), None);
505 assert_eq!(file.line_bytes(0), None);
506 }
507
508 #[test]
509 fn a_carriage_return_is_not_part_of_the_line() {
510 let (map, ids) = map_with(&[("a.c", "one\r\ntwo\r\n")]);
511 let file = map.file(ids[0]);
512 assert_eq!(file.line_bytes(1), Some(&b"one"[..]));
513 assert_eq!(file.line_bytes(2), Some(&b"two"[..]));
514 }
515
516 #[test]
517 fn an_empty_file_has_one_position_and_no_lines_to_read() {
518 let (map, ids) = map_with(&[("a.c", "")]);
519 let file = map.file(ids[0]);
520 assert!(file.is_empty());
521 assert_eq!(map.lookup(file.start).unwrap().line, 1);
522 assert_eq!(file.line_bytes(1), Some(&b""[..]));
523 assert_eq!(file.line_bytes(2), None);
524 }
525
526 #[test]
527 fn a_line_span_covers_the_terminator() {
528 let (map, ids) = map_with(&[("a.c", "one\ntwo\n")]);
529 let file = map.file(ids[0]);
530 assert_eq!(file.line_span(file.start + 1), Some(Span::new(0, 4)));
531 assert_eq!(file.line_span(file.start + 5), Some(Span::new(4, 8)));
532 }
533
534 #[test]
535 fn the_include_stack_runs_from_the_innermost_out() {
536 let mut map = SourceMap::new();
537 let main = map.add("main.c", b"#include <a.h>\n".to_vec()).unwrap();
538 let outer = Span::new(map.file(main).start, map.file(main).start + 14);
539 let a = map.add_included("a.h", b"#include <b.h>\n".to_vec(), outer).unwrap();
540 let inner = Span::new(map.file(a).start, map.file(a).start + 14);
541 let b = map.add_included("b.h", b"int x;\n".to_vec(), inner).unwrap();
542 let stack = map.include_stack(map.file(b).start);
543 assert_eq!(stack, vec![inner, outer]);
544 assert_eq!(map.lookup(stack[0].lo).unwrap().file, a);
545 assert_eq!(map.lookup(stack[1].lo).unwrap().file, main);
546 assert!(map.include_stack(outer.lo).is_empty());
547 }
548
549 #[test]
550 fn a_file_that_does_not_fit_is_refused_rather_than_wrapped() {
551 let mut map = SourceMap::new();
552 map.add("a.c", b"x".to_vec()).unwrap();
553 map.next = BytePos::MAX - 2;
556 assert_eq!(map.add("b.c", b"xx".to_vec()), Err(SourceMapFull));
557 assert_eq!(map.files().len(), 1);
558 }
559
560 #[test]
561 fn contents_can_be_anything_that_is_a_slice_of_bytes() {
562 struct Mapped(&'static [u8]);
565 impl AsRef<[u8]> for Mapped {
566 fn as_ref(&self) -> &[u8] {
567 self.0
568 }
569 }
570 let mut map = SourceMap::new();
571 let id = map.add("a.c", Mapped(b"int x;\n")).unwrap();
572 assert_eq!(map.file(id).bytes(), b"int x;\n");
573 assert_eq!(map.file(id).line_count(), 1);
574 }
575}