Skip to main content

moxy_token/source/
file.rs

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