Skip to main content

base_markdown/
incremental_parser.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! Emit semantic events for Markdown that arrives in pieces.
5use crate::block_parser::{Blocks, TopLevel};
6use crate::definitions::{Definitions, Seed};
7use crate::event_parser::EventParser;
8use crate::stream::{Event, EventKind, Tag, TagKind};
9use crate::ParseOptions;
10use alloc::string::String;
11use alloc::vec::Vec;
12
13/// Parse Markdown that arrives in pieces, emitting the events of each document-level block once no later text can change them.
14///
15/// Push text as it arrives; each call returns the events of blocks that have become stable. Call [`IncrementalParser::finish`] at the end of the input to receive the remaining events. Events are the document's body events; the document start and end events are not emitted. Text values refer to byte ranges of the accumulated [`IncrementalParser::source`], exactly as [`EventParser`] events refer to its source.
16///
17/// ```
18/// use base_markdown::stream::{EventKind, Tag};
19/// use base_markdown::{IncrementalParser, ParseOptions};
20///
21/// let options = ParseOptions::gfm();
22/// let mut parser = IncrementalParser::new(&options);
23///
24/// // An ATX heading is complete at the end of its line.
25/// let events: Vec<_> = parser.push("# Title\nFirst para").collect();
26/// assert!(matches!(events[0].kind, EventKind::Start(Tag::Heading(1))));
27/// assert_eq!(parser.stable_offset(), 8);
28///
29/// // A paragraph remains open until a blank line or another block follows it.
30/// assert_eq!(parser.push("graph.\n").count(), 0);
31/// let events: Vec<_> = parser.push("\nSecond").collect();
32/// assert!(events.iter().any(|event| matches!(event.kind, EventKind::Start(Tag::Paragraph { .. }))));
33/// assert_eq!(&parser.source()[..parser.stable_offset()], "# Title\nFirst paragraph.\n");
34///
35/// // Finishing emits the rest of the document.
36/// let rest: Vec<_> = parser.finish().collect();
37/// assert!(matches!(rest.last().unwrap().kind, EventKind::End(_)));
38/// ```
39///
40/// # Stability
41///
42/// The events emitted across all pushes and the final [`IncrementalParser::finish`] are the events that [`EventParser`] yields for the accumulated source, without the document start and end events, provided that no link reference or footnote call is resolved by a definition in a later document-level block. Stability is defined by block structure: a block is emitted when it and every block before it are closed and no appended text can reinterpret them. Emitted events are never retracted.
43///
44/// Reference resolution is scoped to make the output independent of how the text is divided. Inline content in a document-level block resolves against the definitions that begin before the end of that block, including definitions inside the block itself. A reference to a definition in a later document-level block remains literal text, whereas [`EventParser`] resolves forward references across the whole document.
45///
46/// Container contents are not emitted separately: a list is emitted once it ends, because its tightness depends on every item. Frontmatter holds back all output until its closing fence arrives.
47#[derive(Debug)]
48pub struct IncrementalParser<'a> {
49    source: String,
50    options: &'a ParseOptions,
51    // Owned definitions authored before `anchor`.
52    seed: Seed,
53    // The line of a clean document-level block from which preparation restarts.
54    anchor: usize,
55    // How many document-level blocks from `anchor` have been emitted; the separators after the last of them are not.
56    emitted: usize,
57    // The end of the complete lines already prepared.
58    complete: usize,
59    stable: usize,
60    events: Vec<Event<'static>>,
61    finished: bool,
62}
63
64impl<'a> IncrementalParser<'a> {
65    /// Create a parser for an empty document.
66    pub fn new(options: &'a ParseOptions) -> Self {
67        Self {
68            source: String::new(),
69            options,
70            seed: Seed::default(),
71            anchor: 0,
72            emitted: 0,
73            complete: 0,
74            stable: 0,
75            events: Vec::new(),
76            finished: false,
77        }
78    }
79
80    /// The accumulated source, which emitted text values refer to.
81    pub fn source(&self) -> &str {
82        &self.source
83    }
84
85    /// The byte offset where the last emitted block ends. Appended text cannot change the events of the source before this offset.
86    pub fn stable_offset(&self) -> usize {
87        self.stable
88    }
89
90    /// Append text and return the events of blocks that have become stable.
91    ///
92    /// The text is parsed before this returns; dropping the iterator discards the events it has not yielded.
93    ///
94    /// Only complete lines are prepared, so text without a line ending costs no parsing. Each preparation restarts from the most recent clean document-level block that has been emitted.
95    ///
96    /// # Panics
97    ///
98    /// Panics if called after [`IncrementalParser::finish`].
99    pub fn push(&mut self, text: &str) -> impl Iterator<Item = Event<'static>> + '_ {
100        assert!(!self.finished, "cannot push after finishing");
101        // Earlier text has no line ending after `complete`, except perhaps a final carriage return.
102        let from = self.source.len().saturating_sub(1).max(self.complete);
103        self.source.push_str(text);
104        let complete = complete_lines(&self.source, from);
105        if complete > self.complete {
106            self.complete = complete;
107            self.prepare();
108        }
109        self.events.drain(..)
110    }
111
112    /// End the input and return all remaining events.
113    ///
114    /// # Panics
115    ///
116    /// Panics if called more than once.
117    pub fn finish(&mut self) -> impl Iterator<Item = Event<'static>> + '_ {
118        assert!(!self.finished, "cannot finish twice");
119        self.finished = true;
120        let (blocks, definitions, outline) = Blocks::parse_from(
121            &self.source,
122            self.anchor,
123            self.options,
124            Definitions::seeded(&self.seed),
125        );
126        let parser = EventParser::from_blocks(&self.source, self.options, blocks, definitions);
127        deliver(
128            parser,
129            &outline.blocks,
130            self.emitted,
131            None,
132            &mut self.events,
133        );
134        self.stable = self.source.len();
135        self.events.drain(..)
136    }
137
138    fn prepare(&mut self) {
139        let source = &self.source[..self.complete];
140        if self.anchor == 0
141            && self.emitted == 0
142            && Blocks::frontmatter_pending(source, self.options)
143        {
144            return;
145        }
146        let (blocks, definitions, outline) = Blocks::parse_from(
147            source,
148            self.anchor,
149            self.options,
150            Definitions::seeded(&self.seed),
151        );
152        let tops = &outline.blocks;
153        // A clean block proves every earlier block closed; a trailing blank line at document level closes the last block too.
154        let last = if outline.closed {
155            tops.len().checked_sub(1)
156        } else {
157            (1..tops.len())
158                .rev()
159                .find(|&next| tops[next].clean)
160                .map(|next| next - 1)
161        };
162        let Some(last) = last.filter(|&last| last >= self.emitted) else {
163            return;
164        };
165        // The first prepared block always begins at the clean anchor.
166        let anchor = (0..=last)
167            .rev()
168            .find(|&index| tops[index].clean)
169            .unwrap_or(0);
170        let authored = definitions.before(tops[anchor].line);
171        let parser = EventParser::from_blocks(source, self.options, blocks, definitions);
172        deliver(parser, tops, self.emitted, Some(last), &mut self.events);
173        self.stable = tops[last].end;
174        self.seed.append(authored);
175        self.anchor = tops[anchor].line;
176        self.emitted = last - anchor + 1;
177    }
178}
179
180/// The end of the last complete line, searching for line endings from `from`, or zero if there is none. A trailing carriage return may become part of a CRLF ending, so its line is incomplete.
181fn complete_lines(source: &str, from: usize) -> usize {
182    let bytes = source.as_bytes();
183    let end = bytes.len() - usize::from(bytes.last() == Some(&b'\r'));
184    bytes[from.min(end)..end]
185        .iter()
186        .rposition(|&byte| matches!(byte, b'\n' | b'\r'))
187        .map_or(0, |index| from + index + 1)
188}
189
190/// Move events into `output`, skipping the first `skip` document-level blocks and stopping after block `until`. Separators between blocks are delivered only with the block that follows them, unless delivering to the end of the document.
191fn deliver(
192    mut parser: EventParser<'_>,
193    tops: &[TopLevel],
194    skip: usize,
195    until: Option<usize>,
196    output: &mut Vec<Event<'static>>,
197) {
198    let mut block = 0;
199    let mut depth = 0usize;
200    let mut separators = Vec::new();
201    loop {
202        if depth == 0 {
203            if until.is_some_and(|until| block > until) {
204                return;
205            }
206            parser.set_definition_limit(Some(tops.get(block).map_or(usize::MAX, |top| top.end)));
207        }
208        let Some(Ok(event)) = parser.next() else {
209            break;
210        };
211        match &event.kind {
212            EventKind::Start(Tag::Document(_)) | EventKind::End(TagKind::Document) => continue,
213            EventKind::Separator { .. } if depth == 0 => {
214                separators.push(event);
215                continue;
216            }
217            _ => {}
218        }
219        if depth == 0 {
220            if block >= skip {
221                output.append(&mut separators);
222            } else {
223                separators.clear();
224            }
225        }
226        match event.kind {
227            EventKind::Start(_) => depth += 1,
228            EventKind::End(_) => depth -= 1,
229            _ => {}
230        }
231        if block >= skip {
232            output.push(event);
233        }
234        if depth == 0 {
235            block += 1;
236        }
237    }
238    // Only delivery to the end of the document exhausts the parser, after every skipped block.
239    output.append(&mut separators);
240}
241
242#[cfg(test)]
243mod tests {
244    use super::{complete_lines, deliver, IncrementalParser};
245    use crate::block_parser::Blocks;
246    use crate::definitions::Definitions;
247    use crate::event_parser::EventParser;
248    use crate::stream::Event;
249    use crate::{Constructs, ParseOptions};
250    use alloc::string::String;
251    use alloc::vec::Vec;
252    use core::convert::TryFrom;
253
254    /// A one-shot parse that scopes references to each document-level block, independent of anchors, seeds and stability.
255    fn scoped(source: &str, options: &ParseOptions) -> Vec<Event<'static>> {
256        let (blocks, definitions, outline) =
257            Blocks::parse_from(source, 0, options, Definitions::default());
258        let parser = EventParser::from_blocks(source, options, blocks, definitions);
259        let mut events = Vec::new();
260        deliver(parser, &outline.blocks, 0, None, &mut events);
261        events
262    }
263
264    fn incremental<'a>(
265        pieces: impl IntoIterator<Item = &'a str>,
266        options: &ParseOptions,
267    ) -> Vec<Event<'static>> {
268        let mut parser = IncrementalParser::new(options);
269        let mut events = Vec::new();
270        for piece in pieces {
271            events.extend(parser.push(piece));
272        }
273        events.extend(parser.finish());
274        events
275    }
276
277    #[test]
278    fn definitions_resolve_as_in_a_scoped_one_shot_parse() {
279        const LINES: &[&str] = &[
280            "",
281            "[a] [b] [^n] [c][a] [A]",
282            "text [b]",
283            "[a]: /a",
284            "[A]: /upper",
285            "[b]: /b 'title",
286            "continued'",
287            "[c]:",
288            "/c",
289            "[^n]: note [a]",
290            "    indented [b]",
291            "- [a]: /item",
292            "> [b]: /quote",
293            "- item [a]",
294            "> quote [b]",
295            "---",
296            "===",
297            "# [a]",
298            "|-|",
299            "| [a] | [b] |",
300            "```",
301            "<div>",
302        ];
303        let mut options = ParseOptions::gfm();
304        options.constructs = Constructs {
305            frontmatter: true,
306            ..Constructs::gfm()
307        };
308        let mut state = 0x9e37_79b9_7f4a_7c15_u64;
309        let mut random = move || {
310            state ^= state << 13;
311            state ^= state >> 7;
312            state ^= state << 17;
313            usize::from(u16::try_from(state >> 48).unwrap())
314        };
315        for _ in 0..3000 {
316            let mut source = String::new();
317            for _ in 0..(random() % 14) {
318                source.push_str(LINES[random() % LINES.len()]);
319                source.push_str(if random() % 4 == 0 { "\r\n" } else { "\n" });
320            }
321            let expected = scoped(&source, &options);
322            let characters = source
323                .char_indices()
324                .map(|(index, character)| &source[index..index + character.len_utf8()]);
325            assert_eq!(incremental(characters, &options), expected, "{source:?}");
326            let mut cuts: Vec<usize> = (0..4).map(|_| random() % (source.len() + 1)).collect();
327            cuts.sort_unstable();
328            let mut previous = 0;
329            let mut pieces = Vec::new();
330            for cut in cuts.into_iter().chain([source.len()]) {
331                pieces.push(&source[previous..cut]);
332                previous = cut;
333            }
334            assert_eq!(incremental(pieces, &options), expected, "{source:?}");
335        }
336    }
337
338    #[test]
339    fn complete_lines_exclude_a_trailing_carriage_return() {
340        assert_eq!(complete_lines("", 0), 0);
341        assert_eq!(complete_lines("a", 0), 0);
342        assert_eq!(complete_lines("a\n", 0), 2);
343        assert_eq!(complete_lines("a\nb", 0), 2);
344        assert_eq!(complete_lines("a\r", 0), 0);
345        assert_eq!(complete_lines("a\r\r", 0), 2);
346        assert_eq!(complete_lines("a\r\n", 0), 3);
347        assert_eq!(complete_lines("a\rb", 0), 2);
348        assert_eq!(complete_lines("a\nbc", 2), 0);
349        assert_eq!(complete_lines("a\nb\r\n", 3), 5);
350    }
351
352    #[test]
353    fn preparation_restarts_near_the_end_of_closed_blocks() {
354        let options = ParseOptions::gfm();
355        let mut parser = IncrementalParser::new(&options);
356        for index in 0..200 {
357            let block = match index % 4 {
358                0 => "# Heading\n\n",
359                1 => "A paragraph with *emphasis*.\n\n",
360                2 => "- a list\n- of items\n\n",
361                _ => "```\ncode\n```\n\n",
362            };
363            for character in block.split_inclusive(' ') {
364                let _ = parser.push(character);
365            }
366            // Preparation restarts at most two of these short blocks before the end, independent of the document length.
367            assert!(parser.anchor <= parser.stable_offset());
368            assert!(parser.source.len() - parser.anchor <= 64);
369        }
370    }
371}