Skip to main content

antlr4_runtime/
char_stream.rs

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