Skip to main content

moxy_token/source/
file.rs

1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5use super::Location;
6use crate::span::fallback::Span;
7
8/// Primarily used to map spans (0 based character index ranges)
9/// to bytes.
10#[derive(Debug)]
11pub struct Source {
12    /// the filepath if this source belongs to a file
13    /// on disk.
14    path: Option<PathBuf>,
15
16    /// raw source text
17    text: String,
18
19    /// file-wide lo..hi in proc-macro2 space
20    span: Span,
21
22    /// line start offsets, in char units
23    lines: Vec<usize>,
24
25    /// Cache mapping character indices to UTF-8 byte offsets for efficient span slicing
26    char_to_byte: RefCell<BTreeMap<usize, usize>>,
27}
28
29impl Source {
30    pub(crate) fn new(start: usize, src: impl Into<String>) -> Self {
31        let text = src.into();
32        let mut lines = vec![0];
33        let mut total = 0usize;
34
35        for ch in text.chars() {
36            total += 1;
37
38            if ch == '\n' {
39                lines.push(total);
40            }
41        }
42
43        Self {
44            path: None,
45            text,
46            span: Span::new(start as u32, (start + total) as u32),
47            lines,
48            char_to_byte: RefCell::new(BTreeMap::new()),
49        }
50    }
51
52    pub fn from_file(path: impl Into<PathBuf>) -> std::io::Result<Self> {
53        let path = path.into();
54        let src = std::fs::read_to_string(&path)?;
55        let mut src = Self::new(0, src);
56
57        src.path = Some(path);
58        Ok(src)
59    }
60
61    pub fn path(&self) -> Option<&PathBuf> {
62        self.path.as_ref()
63    }
64
65    pub fn text(&self) -> &str {
66        &self.text
67    }
68
69    pub fn span(&self) -> Span {
70        self.span
71    }
72
73    /// Resolves the given span into a byte index range.
74    pub fn range(&self, span: Span) -> std::ops::Range<usize> {
75        let r = span.byte_range();
76        self.byte(r.start)..self.byte(r.end)
77    }
78
79    /// Gets a sub span of source text from the file.
80    pub fn slice(&self, span: Span) -> String {
81        self.text[self.range(span)].to_owned()
82    }
83
84    /// Resolves a global character index within this file into a 0-based `Location`.
85    pub fn location(&self, i: usize) -> Location {
86        let index = i - self.span.byte_range().start;
87
88        match self.lines.binary_search(&index) {
89            Err(next) => Location::new(index, next - 1, index - self.lines[next - 1]),
90            Ok(line) => Location::new(index, line, 0),
91        }
92    }
93
94    /// Returns the UTF-8 byte index corresponding to a global character index.
95    pub fn byte(&self, i: usize) -> usize {
96        let index = i - self.span.byte_range().start;
97        let mut cache = self.char_to_byte.borrow_mut();
98
99        if let Some(byte_index) = cache.get(&index) {
100            return *byte_index;
101        }
102
103        let (ci, bi) = cache
104            .range(..=index)
105            .next_back()
106            .map(|(&char_index, &byte_index)| (char_index, byte_index))
107            .unwrap_or((0, 0));
108
109        let mut char_index = ci;
110        let mut byte_index = bi;
111
112        for ch in self.text[bi..].chars() {
113            if char_index == index {
114                cache.insert(index, byte_index);
115                return byte_index;
116            }
117
118            char_index += 1;
119            byte_index += ch.len_utf8();
120        }
121
122        cache.insert(index, byte_index);
123        byte_index
124    }
125}
126
127impl Default for Source {
128    fn default() -> Self {
129        Self {
130            path: None,
131            text: String::default(),
132            span: Span::default(),
133            lines: vec![0],
134            char_to_byte: RefCell::new(BTreeMap::default()),
135        }
136    }
137}