1use std::ops::Range;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
4pub struct Span {
5 pub start: u32,
6 pub len: u32,
7}
8
9impl Span {
10 pub const DUMMY: Span = Span { start: 0, len: 0 };
11
12 pub fn new(start: usize, len: usize) -> Self {
13 Span {
14 start: start as u32,
15 len: len as u32,
16 }
17 }
18
19 pub fn end(&self) -> usize {
20 (self.start + self.len) as usize
21 }
22 pub fn range(&self) -> Range<usize> {
23 self.start as usize..self.end()
24 }
25
26 pub fn join(self, other: Span) -> Span {
27 let start = self.start.min(other.start);
28 let end = (self.start + self.len).max(other.start + other.len);
29 Span {
30 start,
31 len: end - start,
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
37pub struct SourceMap {
38 pub path: String,
39 pub source: String,
40 line_starts: Vec<u32>,
41}
42
43impl SourceMap {
44 pub fn new(path: impl Into<String>, source: impl Into<String>) -> Self {
45 let source = source.into();
46 let mut line_starts = vec![0u32];
47 for (i, b) in source.bytes().enumerate() {
48 if b == b'\n' {
49 line_starts.push((i + 1) as u32);
50 }
51 }
52 SourceMap {
53 path: path.into(),
54 source,
55 line_starts,
56 }
57 }
58
59 pub fn line_col(&self, offset: u32) -> (usize, usize) {
60 let idx = match self.line_starts.binary_search(&offset) {
61 Ok(i) => i,
62 Err(i) => i.saturating_sub(1),
63 };
64 let line_start = self.line_starts[idx];
65 let prefix = &self.source.as_bytes()[line_start as usize..offset as usize];
66 let col = std::str::from_utf8(prefix)
67 .map(|s| s.chars().count())
68 .unwrap_or(0)
69 + 1;
70 (idx + 1, col)
71 }
72
73 pub fn line_text(&self, line: usize) -> &str {
74 let start = self.line_starts[line - 1] as usize;
75 let end = self
76 .line_starts
77 .get(line)
78 .map(|n| *n as usize)
79 .unwrap_or(self.source.len());
80 let s = &self.source[start..end];
81 s.trim_end_matches('\n').trim_end_matches('\r')
82 }
83}