Skip to main content

presk_syntax/
source.rs

1//! Source files, byte spans and line/column lookup.
2
3use std::fmt;
4use std::ops::Range;
5
6/// Index of a file in a [`SourceMap`].
7#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
8pub struct FileId(pub u32);
9
10impl fmt::Debug for FileId {
11    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12        write!(f, "f{}", self.0)
13    }
14}
15
16/// A byte range in one source file. Offsets index the original text (not
17/// normalized); only identifier *names* are NFC-normalized by the lexer.
18#[derive(Clone, Copy, PartialEq, Eq, Hash)]
19pub struct Span {
20    pub file: FileId,
21    pub start: u32,
22    pub end: u32,
23}
24
25impl Span {
26    pub fn new(file: FileId, start: usize, end: usize) -> Self {
27        Span { file, start: start as u32, end: end as u32 }
28    }
29
30    /// The smallest span covering `self` and `other` (same file).
31    pub fn to(self, other: Span) -> Span {
32        debug_assert_eq!(self.file, other.file);
33        Span { file: self.file, start: self.start.min(other.start), end: self.end.max(other.end) }
34    }
35
36    pub fn range(self) -> Range<usize> {
37        self.start as usize..self.end as usize
38    }
39
40    pub fn len(self) -> usize {
41        (self.end - self.start) as usize
42    }
43
44    pub fn is_empty(self) -> bool {
45        self.start == self.end
46    }
47}
48
49impl fmt::Debug for Span {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        write!(f, "{}..{}", self.start, self.end)
52    }
53}
54
55pub struct SourceFile {
56    pub name: String,
57    pub text: String,
58    line_starts: Vec<u32>,
59    pub(crate) ariadne: ariadne::Source<String>,
60}
61
62impl SourceFile {
63    fn new(name: String, text: String) -> Self {
64        let mut line_starts = vec![0];
65        for (i, b) in text.bytes().enumerate() {
66            if b == b'\n' {
67                line_starts.push(i as u32 + 1);
68            }
69        }
70        let ariadne = ariadne::Source::from(text.clone());
71        SourceFile { name, text, line_starts, ariadne }
72    }
73
74    /// 1-based line and column (column in characters) of a byte offset.
75    pub fn line_col(&self, offset: u32) -> (usize, usize) {
76        let line = self.line_starts.partition_point(|&s| s <= offset).max(1) - 1;
77        let start = self.line_starts[line] as usize;
78        let col = self.text[start..offset as usize].chars().count();
79        (line + 1, col + 1)
80    }
81
82    pub fn slice(&self, span: Span) -> &str {
83        &self.text[span.range()]
84    }
85}
86
87/// All the files of one compilation.
88#[derive(Default)]
89pub struct SourceMap {
90    files: Vec<SourceFile>,
91}
92
93impl SourceMap {
94    pub fn new() -> Self {
95        Self::default()
96    }
97
98    pub fn add(&mut self, name: impl Into<String>, text: impl Into<String>) -> FileId {
99        let id = FileId(self.files.len() as u32);
100        self.files.push(SourceFile::new(name.into(), text.into()));
101        id
102    }
103
104    /// Read a file from disk and add it; the name is the path as given.
105    pub fn load(&mut self, path: &std::path::Path) -> std::io::Result<FileId> {
106        let text = std::fs::read_to_string(path)?;
107        Ok(self.add(path.display().to_string(), text))
108    }
109
110    pub fn file(&self, id: FileId) -> &SourceFile {
111        &self.files[id.0 as usize]
112    }
113
114    pub fn text(&self, id: FileId) -> &str {
115        &self.file(id).text
116    }
117
118    pub fn slice(&self, span: Span) -> &str {
119        self.file(span.file).slice(span)
120    }
121
122    /// `name:line:col` of the start of a span.
123    pub fn location(&self, span: Span) -> String {
124        let f = self.file(span.file);
125        let (l, c) = f.line_col(span.start);
126        format!("{}:{}:{}", f.name, l, c)
127    }
128
129    pub fn files(&self) -> impl Iterator<Item = (FileId, &SourceFile)> {
130        self.files.iter().enumerate().map(|(i, f)| (FileId(i as u32), f))
131    }
132}