Skip to main content

cqlc_core/
span.rs

1//! Source files and byte-offset spans.
2
3/// Identifies a file in a [`SourceMap`].
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
5pub struct FileId(pub u32);
6
7/// A half-open byte range inside one source file.
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub struct Span {
10    /// The file the span is in.
11    pub file: FileId,
12    /// Byte offset of the first byte.
13    pub start: u32,
14    /// Byte offset after the last byte.
15    pub end: u32,
16}
17
18impl Span {
19    /// Creates a span from byte offsets.
20    pub fn new(file: FileId, start: usize, end: usize) -> Self {
21        Span {
22            file,
23            start: start as u32,
24            end: end as u32,
25        }
26    }
27
28    /// Smallest span covering both `self` and `other` (same file assumed).
29    pub fn to(self, other: Span) -> Span {
30        Span {
31            file: self.file,
32            start: self.start.min(other.start),
33            end: self.end.max(other.end),
34        }
35    }
36
37    /// The span as a byte range.
38    pub fn range(self) -> std::ops::Range<usize> {
39        self.start as usize..self.end as usize
40    }
41}
42
43/// One source file with a line index.
44#[derive(Clone, Debug)]
45pub struct SourceFile {
46    /// Name shown in diagnostics.
47    pub name: String,
48    /// File contents.
49    pub text: String,
50    line_starts: Vec<usize>,
51}
52
53impl SourceFile {
54    /// Creates a source file and indexes its lines.
55    pub fn new(name: impl Into<String>, text: impl Into<String>) -> Self {
56        let text = text.into();
57        let mut line_starts = vec![0];
58        line_starts.extend(text.match_indices('\n').map(|(i, _)| i + 1));
59        SourceFile {
60            name: name.into(),
61            text,
62            line_starts,
63        }
64    }
65
66    /// 1-based line and column (in characters) of a byte offset.
67    pub fn line_col(&self, offset: usize) -> (usize, usize) {
68        let offset = offset.min(self.text.len());
69        let line = match self.line_starts.binary_search(&offset) {
70            Ok(i) => i,
71            Err(i) => i - 1,
72        };
73        let start = self.line_starts[line];
74        let col = self
75            .text
76            .get(start..offset)
77            .map_or(offset - start, |s| s.chars().count());
78        (line + 1, col + 1)
79    }
80
81    /// Text of a 1-based line without its line terminator.
82    pub fn line_text(&self, line: usize) -> &str {
83        let start = self.line_starts[line - 1];
84        let end = self
85            .line_starts
86            .get(line)
87            .copied()
88            .unwrap_or(self.text.len());
89        self.text[start..end].trim_end_matches(['\n', '\r'])
90    }
91
92    /// Byte offset where a 1-based line starts.
93    pub fn line_start(&self, line: usize) -> usize {
94        self.line_starts[line - 1]
95    }
96}
97
98/// All source files of a compilation.
99#[derive(Clone, Debug, Default)]
100pub struct SourceMap {
101    files: Vec<SourceFile>,
102}
103
104impl SourceMap {
105    /// Adds a file and returns its id.
106    pub fn add(&mut self, name: impl Into<String>, text: impl Into<String>) -> FileId {
107        self.files.push(SourceFile::new(name, text));
108        FileId(self.files.len() as u32 - 1)
109    }
110
111    /// Returns the file with the given id.
112    pub fn get(&self, id: FileId) -> &SourceFile {
113        &self.files[id.0 as usize]
114    }
115}