enum_lexer/
cursor.rs

1use std::cmp;
2use std::fmt;
3use std::cell::RefCell;
4use std::str::Chars;
5use std::iter::Peekable;
6
7#[derive(Debug, Clone)]
8pub struct Cursor<'a> {
9    base: u32,
10    token: RefCell<String>,
11    chars: Peekable<Chars<'a>>,
12}
13
14impl<'a> Iterator for Cursor<'a> {
15    type Item = char;
16    #[inline(always)]
17    fn next(&mut self) -> Option<Self::Item> {
18        let ret = self.chars.next();
19        if let Some(ch) = ret {
20            self.token.borrow_mut().push(ch);
21        }
22        ret
23    }
24}
25
26impl<'a> Cursor<'a> {
27    pub fn new_file(name: &str, src: &'a str) -> Cursor<'a> {
28        SOURCE_MAP.with(|cm| {
29            let mut cm = cm.borrow_mut();
30            let span = cm.add_file(name, src);
31            let base = span.lo;
32            Cursor {
33                base,
34                token: RefCell::new(String::new()),
35                chars: src.chars().peekable(),
36            }
37        })
38    }
39
40    #[inline(always)]
41    pub fn peek(&mut self) -> Option<&char> {
42        self.chars.peek()
43    }
44
45    #[inline(always)]
46    pub fn leap(&mut self) {
47        self.chars.next();
48    }
49
50    #[inline(always)]
51    pub fn leap_until(&mut self, func: impl Fn(char) -> bool) {
52        while let Some(&ch) = self.chars.peek() {
53            if func(ch) {
54                break;
55            } else {
56                self.leap();
57            }
58        }
59    }
60    
61    #[inline(always)]
62    pub fn get_token(&mut self) -> (String, Span) {
63        let lo = self.base as u32;
64        self.base += self.token.borrow().len() as u32;
65        let hi = self.base as u32;
66        (self.token.replace(String::new()), Span{ lo, hi } )
67    }
68}
69
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub struct LineColumn {
73    pub line: usize,
74    pub column: usize,
75}
76
77struct FileInfo {
78    name: String,
79    span: Span,
80    lines: Vec<usize>,
81}
82
83impl FileInfo {
84    fn offset_line_column(&self, offset: usize) -> LineColumn {
85        assert!(self.span_within(Span {
86            lo: offset as u32,
87            hi: offset as u32
88        }));
89        let offset = offset - self.span.lo as usize;
90        match self.lines.binary_search(&offset) {
91            Ok(found) => LineColumn {
92                line: found + 1,
93                column: 0,
94            },
95            Err(idx) => LineColumn {
96                line: idx,
97                column: offset - self.lines[idx - 1],
98            },
99        }
100    }
101
102    fn span_within(&self, span: Span) -> bool {
103        span.lo >= self.span.lo && span.hi <= self.span.hi
104    }
105}
106
107/// Computes the offsets of each line in the given source string
108/// and the total number of characters
109fn lines_offsets(s: &str) -> (usize, Vec<usize>) {
110    let mut lines = vec![0];
111    let mut total = 0;
112
113    for ch in s.chars() {
114        total += 1;
115        if ch == '\n' {
116            lines.push(total);
117        }
118    }
119    (total, lines)
120}
121
122
123struct SourceMap {
124    files: Vec<FileInfo>,
125}
126
127impl SourceMap {
128    fn next_start_pos(&self) -> u32 {
129        // Add 1 so there's always space between files.
130        //
131        // We'll always have at least 1 file, as we initialize our files list
132        // with a dummy file.
133        self.files.last().unwrap().span.hi + 1
134    }
135
136    fn add_file(&mut self, name: &str, src: &str) -> Span {
137        let (len, lines) = lines_offsets(src);
138        let lo = self.next_start_pos();
139        let span = Span {
140            lo,
141            hi: lo + (len as u32),
142        };
143
144        self.files.push(FileInfo {
145            name: name.to_owned(),
146            span,
147            lines,
148        });
149
150        span
151    }
152
153    fn fileinfo(&self, span: Span) -> &FileInfo {
154        for file in &self.files {
155            if file.span_within(span) {
156                return file;
157            }
158        }
159        panic!("Invalid span with no related FileInfo!");
160    }
161}
162
163thread_local! {
164    static SOURCE_MAP: RefCell<SourceMap> = RefCell::new(SourceMap {
165        // NOTE: We start with a single dummy file which all call_site() and
166        // def_site() spans reference.
167        files: vec![FileInfo {
168            name: "<unspecified>".to_owned(),
169            span: Span { lo: 0, hi: 0 },
170            lines: vec![0],
171        }],
172    });
173}
174
175#[derive(Clone, Copy, PartialEq, Eq)]
176pub struct Span {
177    pub(crate) lo: u32,
178    pub(crate) hi: u32,
179}
180
181impl Span {
182
183    pub fn source_name(&self) -> String {
184        SOURCE_MAP.with(|cm| {
185            let cm = cm.borrow();
186            let fi = cm.fileinfo(*self);
187            fi.name.clone()
188        })
189    }
190
191    pub fn start(&self) -> LineColumn {
192        SOURCE_MAP.with(|cm| {
193            let cm = cm.borrow();
194            let fi = cm.fileinfo(*self);
195            fi.offset_line_column(self.lo as usize)
196        })
197    }
198
199    pub fn end(&self) -> LineColumn {
200        SOURCE_MAP.with(|cm| {
201            let cm = cm.borrow();
202            let fi = cm.fileinfo(*self);
203            fi.offset_line_column(self.hi as usize)
204        })
205    }
206
207    pub fn join(&self, other: Span) -> Option<Span> {
208        SOURCE_MAP.with(|cm| {
209            let cm = cm.borrow();
210            // If `other` is not within the same FileInfo as us, return None.
211            if !cm.fileinfo(*self).span_within(other) {
212                return None;
213            }
214            Some(Span {
215                lo: cmp::min(self.lo, other.lo),
216                hi: cmp::max(self.hi, other.hi),
217            })
218        })
219    }
220
221    pub fn first_byte(self) -> Self {
222        Span {
223            lo: self.lo,
224            hi: cmp::min(self.lo.saturating_add(1), self.hi),
225        }
226    }
227
228    pub fn last_byte(self) -> Self {
229        Span {
230            lo: cmp::max(self.hi.saturating_sub(1), self.lo),
231            hi: self.hi,
232        }
233    }
234}
235
236impl fmt::Debug for Span {
237    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
238        return write!(f, "bytes({}..{})", self.lo, self.hi);
239    }
240}
241