Skip to main content

antlr4_runtime/
char_stream.rs

1use crate::int_stream::{EOF, IntStream, UNKNOWN_SOURCE_NAME};
2use std::rc::Rc;
3
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub struct TextInterval {
6    pub start: usize,
7    pub stop: usize,
8}
9
10impl TextInterval {
11    pub const fn new(start: usize, stop: usize) -> Self {
12        Self { start, stop }
13    }
14
15    pub const fn empty() -> Self {
16        Self { start: 1, stop: 0 }
17    }
18
19    pub const fn is_empty(self) -> bool {
20        self.start > self.stop
21    }
22}
23
24/// Line/column effect of consuming a half-open character span.
25#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
26pub struct PositionSummary {
27    /// Number of newline characters in the span.
28    pub line_breaks: usize,
29    /// Number of characters after the final newline, or the complete span
30    /// length when no newline is present.
31    pub trailing_columns: usize,
32}
33
34impl PositionSummary {
35    /// Applies this summary to an existing one-based line and zero-based column.
36    pub const fn apply(self, line: usize, column: usize) -> (usize, usize) {
37        let line = line.saturating_add(self.line_breaks);
38        let column = if self.line_breaks == 0 {
39            column.saturating_add(self.trailing_columns)
40        } else {
41            self.trailing_columns
42        };
43        (line, column)
44    }
45}
46
47pub trait CharStream: IntStream {
48    fn text(&self, interval: TextInterval) -> String;
49
50    /// Reads one Unicode scalar at an absolute character index without moving
51    /// the stream cursor.
52    ///
53    /// Returning `None` leaves callers on the compatible `seek` + `la`
54    /// fallback. Implementations that support immutable access return
55    /// [`EOF`] when `index` is outside the input.
56    fn symbol_at(&self, _index: usize) -> Option<i32> {
57        None
58    }
59
60    /// Returns the complete input as ASCII bytes when character and byte
61    /// indexes are identical.
62    fn contiguous_ascii(&self) -> Option<&[u8]> {
63        None
64    }
65
66    /// Summarizes source-position changes for the half-open character interval
67    /// `[start, end)` without moving the stream cursor.
68    ///
69    /// Implementations may clamp `end` to the input size. Returning `None`
70    /// leaves callers on scalar replay.
71    fn position_summary(&self, _start: usize, _end: usize) -> Option<PositionSummary> {
72        None
73    }
74
75    /// Returns the complete backing UTF-8 source when it can be shared with a
76    /// token store.
77    ///
78    /// Implementations that return `None` remain supported, but their token
79    /// text is copied into the store's sparse explicit-text pool.
80    fn source_text(&self) -> Option<Rc<str>> {
81        None
82    }
83
84    fn byte_interval(&self, interval: TextInterval) -> Option<(usize, usize)> {
85        self.text_source_interval(interval)
86            .map(|(_, start, stop)| (start, stop))
87    }
88
89    fn text_source_interval(&self, _interval: TextInterval) -> Option<(Rc<str>, usize, usize)> {
90        None
91    }
92}
93
94#[derive(Clone, Debug)]
95pub struct InputStream {
96    source: Rc<str>,
97    data: InputData,
98    cursor: usize,
99    source_name: String,
100}
101
102#[derive(Clone, Debug)]
103enum InputData {
104    Ascii,
105    Unicode {
106        chars: Vec<char>,
107        byte_offsets: Vec<usize>,
108    },
109}
110
111impl InputData {
112    fn new(input: &str) -> Self {
113        if input.is_ascii() {
114            Self::Ascii
115        } else {
116            Self::Unicode {
117                chars: input.chars().collect(),
118                byte_offsets: input.char_indices().map(|(index, _)| index).collect(),
119            }
120        }
121    }
122
123    const fn len(&self, source: &str) -> usize {
124        match self {
125            Self::Ascii => source.len(),
126            Self::Unicode { chars, .. } => chars.len(),
127        }
128    }
129
130    fn get(&self, source: &str, index: usize) -> Option<char> {
131        match self {
132            Self::Ascii => source.as_bytes().get(index).map(|byte| char::from(*byte)),
133            Self::Unicode { chars, .. } => chars.get(index).copied(),
134        }
135    }
136
137    fn byte_bounds(&self, source: &str, start: usize, stop: usize) -> Option<(usize, usize)> {
138        match self {
139            Self::Ascii => Some((start, stop + 1)),
140            Self::Unicode { byte_offsets, .. } => {
141                let start_byte = *byte_offsets.get(start)?;
142                let stop_byte = byte_offsets.get(stop + 1).copied().unwrap_or(source.len());
143                Some((start_byte, stop_byte))
144            }
145        }
146    }
147}
148
149impl InputStream {
150    /// Creates a character stream from UTF-8 text using ANTLR's unknown source
151    /// name placeholder.
152    pub fn new(input: impl AsRef<str>) -> Self {
153        Self::with_source_name(input, UNKNOWN_SOURCE_NAME)
154    }
155
156    /// Creates a character stream with an explicit source name for tokens and
157    /// diagnostics.
158    pub fn with_source_name(input: impl AsRef<str>, source_name: impl Into<String>) -> Self {
159        let input = input.as_ref();
160        Self {
161            source: Rc::from(input),
162            data: InputData::new(input),
163            cursor: 0,
164            source_name: source_name.into(),
165        }
166    }
167
168    /// Returns true when the cursor has reached or passed the end of input.
169    pub fn is_eof(&self) -> bool {
170        self.cursor >= self.data.len(&self.source)
171    }
172}
173
174impl IntStream for InputStream {
175    fn consume(&mut self) {
176        if !self.is_eof() {
177            self.cursor += 1;
178        }
179    }
180
181    fn la(&mut self, offset: isize) -> i32 {
182        if offset == 0 {
183            return 0;
184        }
185
186        let absolute = if offset > 0 {
187            self.cursor.checked_add((offset - 1).cast_unsigned())
188        } else {
189            offset
190                .checked_neg()
191                .and_then(|distance| usize::try_from(distance).ok())
192                .and_then(|distance| self.cursor.checked_sub(distance))
193        };
194
195        absolute
196            .and_then(|index| self.data.get(&self.source, index))
197            .map_or(EOF, |ch| ch as i32)
198    }
199
200    fn index(&self) -> usize {
201        self.cursor
202    }
203
204    fn seek(&mut self, index: usize) {
205        self.cursor = index.min(self.data.len(&self.source));
206    }
207
208    fn size(&self) -> usize {
209        self.data.len(&self.source)
210    }
211
212    fn source_name(&self) -> &str {
213        &self.source_name
214    }
215}
216
217impl CharStream for InputStream {
218    /// Returns text for an inclusive interval of Unicode scalar indices.
219    fn text(&self, interval: TextInterval) -> String {
220        if let Some((source, start, stop)) = self.text_source_interval(interval) {
221            return source[start..stop].to_owned();
222        }
223        String::new()
224    }
225
226    fn symbol_at(&self, index: usize) -> Option<i32> {
227        Some(
228            self.data
229                .get(&self.source, index)
230                .map_or(EOF, |ch| u32::from(ch).cast_signed()),
231        )
232    }
233
234    fn contiguous_ascii(&self) -> Option<&[u8]> {
235        matches!(self.data, InputData::Ascii).then(|| self.source.as_bytes())
236    }
237
238    fn position_summary(&self, start: usize, end: usize) -> Option<PositionSummary> {
239        if start > end {
240            return None;
241        }
242        let len = self.data.len(&self.source);
243        let start = start.min(len);
244        let end = end.min(len);
245
246        let mut summary = PositionSummary::default();
247        let mut note = |is_newline| {
248            if is_newline {
249                summary.line_breaks += 1;
250                summary.trailing_columns = 0;
251            } else {
252                summary.trailing_columns += 1;
253            }
254        };
255        match &self.data {
256            InputData::Ascii => {
257                for &byte in &self.source.as_bytes()[start..end] {
258                    note(byte == b'\n');
259                }
260            }
261            InputData::Unicode { chars, .. } => {
262                for &ch in &chars[start..end] {
263                    note(ch == '\n');
264                }
265            }
266        }
267        Some(summary)
268    }
269
270    fn text_source_interval(&self, interval: TextInterval) -> Option<(Rc<str>, usize, usize)> {
271        let len = self.data.len(&self.source);
272        if interval.is_empty() || len == 0 {
273            return None;
274        }
275
276        let start = interval.start.min(len);
277        let stop = interval.stop.min(len.saturating_sub(1));
278        if start > stop {
279            return None;
280        }
281
282        let (start_byte, stop_byte) = self.data.byte_bounds(&self.source, start, stop)?;
283        Some((Rc::clone(&self.source), start_byte, stop_byte))
284    }
285
286    fn source_text(&self) -> Option<Rc<str>> {
287        Some(Rc::clone(&self.source))
288    }
289
290    fn byte_interval(&self, interval: TextInterval) -> Option<(usize, usize)> {
291        let len = self.data.len(&self.source);
292        if interval.is_empty() || len == 0 {
293            return None;
294        }
295        let start = interval.start.min(len);
296        let stop = interval.stop.min(len.saturating_sub(1));
297        (start <= stop)
298            .then(|| self.data.byte_bounds(&self.source, start, stop))
299            .flatten()
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn lookahead_and_text_are_codepoint_indexed() {
309        let mut input = InputStream::with_source_name("aβ\n", "sample");
310        assert_eq!(input.source_name(), "sample");
311        assert_eq!(input.size(), 3);
312        assert_eq!(input.la(1), 'a' as i32);
313        assert_eq!(input.la(2), 'β' as i32);
314        assert_eq!(input.text(TextInterval::new(0, 1)), "aβ");
315        input.consume();
316        assert_eq!(input.index(), 1);
317        assert_eq!(input.la(-1), 'a' as i32);
318        assert_eq!(input.la(isize::MIN), EOF);
319        input.seek(99);
320        assert_eq!(input.la(1), EOF);
321    }
322
323    #[test]
324    fn optional_fast_paths_preserve_scalar_indexes_and_positions() {
325        let ascii = InputStream::new("ab\ncd");
326        assert_eq!(ascii.contiguous_ascii(), Some(&b"ab\ncd"[..]));
327        assert_eq!(ascii.symbol_at(2), Some('\n' as i32));
328        assert_eq!(ascii.symbol_at(5), Some(EOF));
329        assert_eq!(
330            ascii.position_summary(1, 5),
331            Some(PositionSummary {
332                line_breaks: 1,
333                trailing_columns: 2,
334            })
335        );
336        assert_eq!(
337            ascii.position_summary(5, 99),
338            Some(PositionSummary::default())
339        );
340        assert_eq!(ascii.position_summary(4, 2), None);
341        assert_eq!(ascii.position_summary(7, 6), None);
342
343        let unicode = InputStream::new("aβ\nγ");
344        assert_eq!(unicode.contiguous_ascii(), None);
345        assert_eq!(unicode.symbol_at(1), Some('β' as i32));
346        assert_eq!(unicode.symbol_at(4), Some(EOF));
347        assert_eq!(
348            unicode.position_summary(1, 4),
349            Some(PositionSummary {
350                line_breaks: 1,
351                trailing_columns: 1,
352            })
353        );
354    }
355
356    #[test]
357    fn position_summary_applies_to_existing_coordinates() {
358        assert_eq!(
359            PositionSummary {
360                line_breaks: 0,
361                trailing_columns: 3,
362            }
363            .apply(4, 7),
364            (4, 10)
365        );
366        assert_eq!(
367            PositionSummary {
368                line_breaks: 2,
369                trailing_columns: 3,
370            }
371            .apply(4, 7),
372            (6, 3)
373        );
374    }
375}