Skip to main content

antlr4_runtime/
byte_stream.rs

1//! A byte-oriented [`CharStream`] for parsing binary formats.
2//!
3//! ANTLR grammars normally consume Unicode text, but many real-world formats
4//! are raw bytes: chunk containers (RIFF/WAV), fixed-width records (tar), and
5//! self-describing tag streams (CBOR, Standard MIDI). The reference runtimes
6//! parse these by treating each byte as a codepoint in `U+0000..=U+00FF` (a
7//! "Latin-1" view) and writing lexer rules over `' '..'ÿ'`.
8//!
9//! [`InputStream`](crate::InputStream) can do this too, but only after decoding
10//! the bytes into a `String`: any byte `>= 0x80` is not valid UTF-8 on its own,
11//! so the whole input takes the non-ASCII path and is materialized into a
12//! `Vec<char>` plus a byte-offset table — roughly 12 bytes of heap per input
13//! byte, and the compiled-DFA ASCII scanner is disabled.
14//!
15//! `ByteStream` avoids all of that. It is generic over any `AsRef<[u8]>`
16//! backing store, so stream index equals byte offset, lookahead is a single
17//! array read, and there is no transcoding or auxiliary allocation.
18//!
19//! # Mapping to Rust IO primitives
20//!
21//! ANTLR parsing needs random access — the lexer and parser `seek`, look
22//! behind with `la(-1)`, and `mark`/`release` for prediction — so the bytes
23//! must live fully in memory; `ByteStream` cannot lazily pull from a socket
24//! mid-parse. The design instead meets the two IO shapes that matter:
25//!
26//! - **Bytes you already hold** (a network read buffer, an `mmap`, a slice of a
27//!   larger frame): borrow them zero-copy with `ByteStream::new(&buf[..])`.
28//!   Nothing is copied; the stream lives as long as the borrow.
29//! - **A reader** (`File`, `TcpStream`, `Stdin`, `Cursor`): drain it into an
30//!   owned buffer with [`ByteStream::from_reader`], which is just a thin
31//!   wrapper over [`std::io::Read::read_to_end`].
32//! - **An owned `Vec<u8>`**: hand it over with `ByteStream::new(vec)` and the
33//!   stream takes ownership without copying.
34//!
35//! ```ignore
36//! // From a file:
37//! let stream = ByteStream::from_reader(std::fs::File::open(path)?)?;
38//! // Zero-copy from an in-memory buffer (e.g. bytes read off a socket):
39//! let stream = ByteStream::new(&packet[..]);
40//!
41//! // Feed it to any generated lexer built from a byte-oriented grammar.
42//! let lexer = FooLexer::new(stream);
43//! let tokens = CommonTokenStream::new(lexer);
44//! let mut parser = FooParser::new(tokens);
45//! let tree = parser.entry_rule()?;
46//! ```
47//!
48//! Write lexer rules against the byte range, e.g. `BYTE : ' ' .. 'ÿ';`. A
49//! complete worked example — a Standard MIDI File grammar parsed over a
50//! `ByteStream` — lives under `tests/fixtures/antlr4-rust-gen/midi-binary/`.
51//!
52//! # Token text is hex
53//!
54//! Because the bytes are not text, [`CharStream::text`] renders the matched
55//! span as a lowercase hex string with no separators (`[0xDE, 0xAD]` becomes
56//! `"dead"`). Token *positions* are still exact byte offsets; use
57//! [`IntStream::index`](crate::IntStream::index) or a token's byte span when you
58//! need to slice the original bytes.
59
60use std::io;
61
62use crate::char_stream::{CharStream, PositionSummary, TextInterval};
63use crate::int_stream::{EOF, IntStream, UNKNOWN_SOURCE_NAME};
64
65/// A [`CharStream`] backed by raw bytes, where each byte is one symbol in
66/// `0..=255` and the stream index is the byte offset.
67///
68/// Generic over the backing store `B: AsRef<[u8]>`: use `Vec<u8>` for owned
69/// bytes or `&[u8]` to borrow an existing buffer zero-copy. See the
70/// [module documentation](self) for how this maps onto Rust IO primitives.
71#[derive(Clone, Debug)]
72pub struct ByteStream<B = Vec<u8>> {
73    bytes: B,
74    cursor: usize,
75    source_name: String,
76}
77
78impl ByteStream<Vec<u8>> {
79    /// Creates a byte stream by draining a [`std::io::Read`] into an owned
80    /// buffer — the bridge for files, sockets, stdin, and [`std::io::Cursor`].
81    ///
82    /// # Errors
83    ///
84    /// Returns any error produced while reading `reader` to end.
85    pub fn from_reader(mut reader: impl io::Read) -> io::Result<Self> {
86        let mut bytes = Vec::new();
87        reader.read_to_end(&mut bytes)?;
88        Ok(Self::new(bytes))
89    }
90}
91
92impl<B: AsRef<[u8]>> ByteStream<B> {
93    /// Creates a byte stream over `bytes`, using ANTLR's unknown source-name
94    /// placeholder.
95    ///
96    /// `bytes` may be an owned `Vec<u8>` or a borrowed `&[u8]` (zero-copy).
97    pub fn new(bytes: B) -> Self {
98        Self::with_source_name(bytes, UNKNOWN_SOURCE_NAME)
99    }
100
101    /// Creates a byte stream with an explicit source name for tokens and
102    /// diagnostics.
103    pub fn with_source_name(bytes: B, source_name: impl Into<String>) -> Self {
104        Self {
105            bytes,
106            cursor: 0,
107            source_name: source_name.into(),
108        }
109    }
110
111    /// Returns the backing bytes.
112    #[must_use]
113    pub fn bytes(&self) -> &[u8] {
114        self.bytes.as_ref()
115    }
116
117    /// Returns true when the cursor has reached or passed the end of input.
118    #[must_use]
119    pub fn is_eof(&self) -> bool {
120        self.cursor >= self.bytes.as_ref().len()
121    }
122}
123
124impl<B: AsRef<[u8]>> IntStream for ByteStream<B> {
125    fn consume(&mut self) {
126        if !self.is_eof() {
127            self.cursor += 1;
128        }
129    }
130
131    fn la(&mut self, offset: isize) -> i32 {
132        if offset == 0 {
133            return 0;
134        }
135
136        // Mirror `InputStream::la`: `+1` is the symbol under the cursor, and
137        // negative offsets look behind. `checked_*` keeps `isize::MIN` and
138        // out-of-range lookahead on the EOF path instead of panicking.
139        let absolute = if offset > 0 {
140            self.cursor.checked_add((offset - 1).cast_unsigned())
141        } else {
142            offset
143                .checked_neg()
144                .and_then(|distance| usize::try_from(distance).ok())
145                .and_then(|distance| self.cursor.checked_sub(distance))
146        };
147
148        absolute.map_or(EOF, |index| self.symbol_at(index).unwrap_or(EOF))
149    }
150
151    fn index(&self) -> usize {
152        self.cursor
153    }
154
155    fn seek(&mut self, index: usize) {
156        self.cursor = index.min(self.bytes.as_ref().len());
157    }
158
159    fn size(&self) -> usize {
160        self.bytes.as_ref().len()
161    }
162
163    fn source_name(&self) -> &str {
164        &self.source_name
165    }
166}
167
168impl<B: AsRef<[u8]>> CharStream for ByteStream<B> {
169    /// Renders the inclusive byte interval as a lowercase, separator-free hex
170    /// string. See the [module documentation](self) for the rationale.
171    fn text(&self, interval: TextInterval) -> String {
172        // Clamp `stop` before any `+1`, mirroring `InputStream`: a caller
173        // passing `TextInterval::new(_, usize::MAX)` (e.g. an EOF token span)
174        // must not overflow.
175        let bytes = self.bytes.as_ref();
176        let len = bytes.len();
177        if interval.is_empty() || len == 0 {
178            return String::new();
179        }
180        let start = interval.start.min(len);
181        let stop = interval.stop.min(len - 1);
182        if start > stop {
183            return String::new();
184        }
185        use std::fmt::Write as _;
186        bytes[start..=stop].iter().fold(
187            String::with_capacity((stop - start + 1) * 2),
188            |mut acc, byte| {
189                // Writing to a String is infallible.
190                let _ = write!(acc, "{byte:02x}");
191                acc
192            },
193        )
194    }
195
196    fn symbol_at(&self, index: usize) -> Option<i32> {
197        Some(
198            self.bytes
199                .as_ref()
200                .get(index)
201                .map_or(EOF, |&byte| i32::from(byte)),
202        )
203    }
204
205    // NOTE: `contiguous_ascii` is deliberately NOT implemented. That fast path
206    // feeds bytes into a 128-wide ASCII DFA row (`ascii_target`), which is only
207    // valid for 7-bit input; bytes `>= 0x80` route correctly through the
208    // generic path's `wide_rows` instead.
209
210    /// Summarizes line/column movement over `[start, end)` by scanning the raw
211    /// bytes.
212    ///
213    /// Without this, [`BaseLexer`](crate::lexer::BaseLexer) would fall back to
214    /// iterating [`Self::text`], which is hex — so a span of N bytes would count
215    /// as 2N columns and `0x0A` newline bytes would be invisible, corrupting the
216    /// line/column of split or synthesized tokens.
217    fn position_summary(&self, start: usize, end: usize) -> Option<PositionSummary> {
218        let bytes = self.bytes.as_ref();
219        let len = bytes.len();
220        if start > end {
221            return None;
222        }
223        let start = start.min(len);
224        let end = end.min(len);
225        let mut summary = PositionSummary::default();
226        for &byte in &bytes[start..end] {
227            if byte == b'\n' {
228                summary.line_breaks += 1;
229                summary.trailing_columns = 0;
230            } else {
231                summary.trailing_columns += 1;
232            }
233        }
234        Some(summary)
235    }
236
237    fn byte_interval(&self, interval: TextInterval) -> Option<(usize, usize)> {
238        // Index == byte offset, so the byte span is exact. Clamp `stop` before
239        // the `+1` for the same overflow reason as `text`.
240        let len = self.bytes.as_ref().len();
241        if interval.is_empty() || len == 0 {
242            return None;
243        }
244        let start = interval.start.min(len);
245        let stop = interval.stop.min(len - 1);
246        (start <= stop).then_some((start, stop + 1))
247    }
248}
249
250#[cfg(test)]
251#[allow(clippy::disallowed_methods)] // insta assertion macros unwrap internal I/O.
252mod tests {
253    use super::*;
254
255    #[test]
256    fn lookahead_reads_bytes_including_high_bytes() {
257        let mut stream = ByteStream::new(vec![0x00, 0x7F, 0x80, 0xFF]);
258        assert_eq!(stream.la(0), 0, "la(0) is the ANTLR sentinel, not EOF");
259        assert_eq!(stream.la(1), 0x00);
260        assert_eq!(stream.la(2), 0x7F);
261        assert_eq!(stream.la(3), 0x80, "high byte is 128, not sign-extended");
262        assert_eq!(stream.la(4), 0xFF);
263        assert_eq!(stream.la(5), EOF);
264        stream.consume();
265        assert_eq!(stream.index(), 1);
266        assert_eq!(stream.la(-1), 0x00);
267        assert_eq!(stream.la(isize::MIN), EOF, "no panic on extreme offset");
268    }
269
270    #[test]
271    fn consume_stops_at_eof_and_seek_clamps() {
272        let mut stream = ByteStream::new(vec![0x01, 0x02]);
273        assert_eq!(stream.size(), 2);
274        stream.consume();
275        stream.consume();
276        stream.consume(); // past EOF is a no-op
277        assert_eq!(stream.index(), 2);
278        assert!(stream.is_eof());
279        stream.seek(99);
280        assert_eq!(stream.index(), 2, "seek clamps to size");
281        stream.seek(1);
282        assert_eq!(stream.la(1), 0x02);
283    }
284
285    #[test]
286    fn text_is_lowercase_hex_and_byte_interval_is_exact() {
287        let stream = ByteStream::new(vec![0xDE, 0xAD, 0xBE, 0xEF]);
288        assert_eq!(stream.text(TextInterval::new(0, 3)), "deadbeef");
289        assert_eq!(stream.text(TextInterval::new(1, 2)), "adbe");
290        assert_eq!(stream.text(TextInterval::empty()), "");
291        // Inclusive char interval [1, 2] -> half-open byte span [1, 3).
292        assert_eq!(stream.byte_interval(TextInterval::new(1, 2)), Some((1, 3)));
293        assert_eq!(stream.byte_interval(TextInterval::empty()), None);
294        assert_eq!(stream.symbol_at(0), Some(0xDE));
295        assert_eq!(stream.symbol_at(4), Some(EOF));
296    }
297
298    #[test]
299    fn text_and_byte_interval_clamp_usize_max_without_overflow() {
300        // An EOF-token span can carry `stop == usize::MAX`; clamping before the
301        // `+1` must not overflow (debug panic / release wrap).
302        let stream = ByteStream::new(vec![0xDE, 0xAD]);
303        assert_eq!(stream.text(TextInterval::new(0, usize::MAX)), "dead");
304        assert_eq!(
305            stream.byte_interval(TextInterval::new(0, usize::MAX)),
306            Some((0, 2)),
307        );
308        // Out-of-range start clamps to empty rather than panicking.
309        assert_eq!(stream.text(TextInterval::new(5, usize::MAX)), "");
310    }
311
312    #[test]
313    fn position_summary_scans_raw_bytes_not_hex() {
314        // Bytes, not hex: a newline byte (0x0A) is one line break, and each
315        // other byte is one column — never doubled as the hex rendering would.
316        let stream = ByteStream::new(vec![0x41, 0x0A, 0x42, 0x43]);
317        assert_eq!(
318            stream.position_summary(0, 4),
319            Some(PositionSummary {
320                line_breaks: 1,
321                trailing_columns: 2,
322            }),
323        );
324        // No newline in [2, 4): two raw bytes are two columns (hex would say
325        // four).
326        assert_eq!(
327            stream.position_summary(2, 4),
328            Some(PositionSummary {
329                line_breaks: 0,
330                trailing_columns: 2,
331            }),
332        );
333        assert_eq!(stream.position_summary(4, 2), None);
334    }
335
336    #[test]
337    fn borrows_bytes_zero_copy() {
338        // The network-buffer case: parse a slice we already hold without
339        // handing ownership to the stream.
340        let buffer: [u8; 4] = [0xCA, 0xFE, 0xBA, 0xBE];
341        let mut stream = ByteStream::new(&buffer[..]);
342        assert_eq!(stream.la(1), 0xCA);
343        assert_eq!(stream.size(), 4);
344        // `buffer` is still ours afterwards.
345        assert_eq!(buffer[0], 0xCA);
346    }
347
348    #[test]
349    fn from_reader_drains_any_read() {
350        // The file/socket case, exercised here with an in-memory Cursor that
351        // implements the same `io::Read` contract as `File`/`TcpStream`.
352        let source = io::Cursor::new(vec![0x4D, 0x54, 0x68, 0x64]); // "MThd"
353        let mut stream = ByteStream::from_reader(source).expect("cursor read is infallible");
354        assert_eq!(stream.size(), 4);
355        assert_eq!(stream.la(1), 0x4D);
356        assert_eq!(stream.text(TextInterval::new(0, 3)), "4d546864");
357    }
358}