Skip to main content

cortexkit_rich_ui_syntax/
stream.rs

1//! Incremental reply parsing.
2//!
3//! [`StreamParser`] keeps every received byte (up to the reply limit) for one
4//! whole-source parse at [`StreamParser::finish`], so the final result never
5//! depends on how the source was chunked. While a reply streams in, it also
6//! runs a separate *provisional* machine that turns each byte, once, into
7//! bounded block and inline state, and reports a display-only [`Revision`]
8//! whenever the drawable picture of the received prefix changes.
9//!
10//! The provisional machine never reparses the received prefix. Text whose
11//! classification is still open (a code span waiting for its closing
12//! backticks, a link waiting for its destination, a tag waiting for its
13//! closing tag, a possible reference, the start of a line that may still
14//! become a block marker) is held in a bounded buffer and is re-examined at
15//! most a small constant number of times, when it resolves. Completed tags
16//! and links are classified by the same parser [`crate::parse`] uses, run once
17//! over just their buffered text.
18
19use crate::parser::{parse_bytes, source_cap, Engine};
20use crate::{parse, Document, Kind, Markdown, Mode, Node, Span, EMPHASIS_DEPTH, MARKDOWN_DEPTH};
21use std::collections::VecDeque;
22use std::sync::Arc;
23
24/// The most source bytes one unresolved provisional context may hold: a code
25/// span, a link, a whole tag, a table header line, or a line start that may
26/// still become a block marker. At this size the held text is drawn as a
27/// provisional literal and dropped. Final parsing has no such threshold.
28pub const STREAM_CONTEXT_BYTES: usize = 32 * 1024;
29/// The most source bytes a tag's opening (`<Name …>`) may hold before it is
30/// drawn as a provisional literal.
31pub const TAG_OPENING_BYTES: usize = 4 * 1024;
32/// The most characters a possible `$$tc(N)` reference may hold before it is
33/// drawn as a provisional literal. This is a streaming threshold, not a limit
34/// on reference numbers: the final parse accepts any length.
35pub const PENDING_REFERENCE_CHARS: usize = 24;
36
37/// Text nodes are split at this many bytes so that drawing a revision copies
38/// a bounded amount of text from the run still being written.
39const RUN_BYTES: usize = 1024;
40/// Link labels nest brackets at most this deep before the held link is drawn
41/// as a provisional literal. This bounds how often a failed link's text is
42/// re-examined.
43const LABEL_DEPTH: usize = 4;
44/// Quote and list containers open at once, counting a list and its item as
45/// two.
46const CONTAINERS: usize = 2 * MARKDOWN_DEPTH;
47
48/// The work one [`StreamParser::feed`] call did, as plain counts. The caller
49/// does its own fuel accounting: it reserves part of a reply's budget for the
50/// final parse and charges provisional work separately.
51#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
52#[non_exhaustive]
53pub struct Work {
54    /// Received bytes consumed by the provisional machine. Each byte is
55    /// scanned once; bytes past the reply limit are not scanned.
56    pub scanned: usize,
57    /// Bytes of held, unresolved text examined again: when a held context
58    /// resolves, fails, or reaches its cap, or when a completed tag or link
59    /// is classified.
60    pub reexamined: usize,
61}
62
63/// A provisional picture of a streaming reply.
64///
65/// A revision is a pure function of the received prefix: the same bytes give
66/// an equal revision however they were chunked. Revisions are **not**
67/// monotone; a later byte may reclassify text drawn by an earlier revision
68/// (a closing backtick, a link destination, a table delimiter row, the rest
69/// of a line), so a caller drawing revisions replaces the whole drawn
70/// revision with the next one. Text still held unresolved is not drawn at all.
71///
72/// A revision is inert. It carries display structure only: no reference
73/// numbers (a reference is a [`Display::PendingReference`] placeholder), no
74/// link destinations, no tag props, and nothing that names data to fetch or
75/// an action to take. It is never a storage-time reference decision; use
76/// [`StreamParser::finish`] or [`crate::scan_references`] for that.
77#[derive(Clone, Debug, Default, PartialEq, Eq)]
78#[non_exhaustive]
79pub struct Revision {
80    pub nodes: Vec<Arc<DisplayNode>>,
81}
82
83/// One node of a provisional display tree. Spans are byte offsets into the
84/// received text after the same UTF-8 repair [`crate::parse_bytes`] applies:
85/// each maximal invalid byte subsequence becomes one U+FFFD.
86#[derive(Clone, Debug, PartialEq, Eq)]
87#[non_exhaustive]
88pub struct DisplayNode {
89    pub span: Span,
90    pub kind: Display,
91    pub children: Vec<Arc<DisplayNode>>,
92}
93
94/// What a provisional display node draws.
95#[derive(Clone, Debug, PartialEq, Eq)]
96#[non_exhaustive]
97pub enum Display {
98    Paragraph,
99    Heading(u8),
100    ThematicBreak,
101    /// Fenced code; its text is in `Text` children.
102    CodeBlock {
103        language: String,
104    },
105    Quote,
106    List {
107        /// The number of an ordered list's first item; `None` for a bullet
108        /// list.
109        start: Option<u64>,
110    },
111    Item,
112    /// A task list marker. Only a completed tag's body, which the final
113    /// parser classifies, produces one; while streaming, a list item's
114    /// `[ ]` or `[x]` is drawn as text.
115    Task(bool),
116    Table,
117    TableHead,
118    TableRow,
119    TableCell,
120    Emphasis,
121    Strong,
122    Strikethrough,
123    Code(String),
124    /// A link's or image's label, drawn as text. The destination is withheld.
125    Link {
126        image: bool,
127    },
128    Text(String),
129    /// Opaque or provisional literal source text, control characters
130    /// replaced.
131    Literal(String),
132    SoftBreak,
133    HardBreak,
134    /// A complete tag, by name only: props are withheld.
135    Tag {
136        name: String,
137        block: bool,
138    },
139    /// Where a tool-output reference stands. Its number is withheld.
140    PendingReference,
141}
142
143/// Parses a source that arrives in chunks.
144///
145/// [`finish`](Self::finish) returns exactly what [`parse_bytes`] returns for
146/// the whole received input, for every chunking: tree, references,
147/// diagnostics and spans. Bytes beyond the mode's size limit, plus a
148/// three-byte margin for a split UTF-8 character, are counted but not kept;
149/// `finish` then marks the document shortened, and in program mode refuses
150/// it (`E_SOURCE_TOO_LARGE`, no nodes), so a program never runs from a
151/// cut-off prefix.
152///
153/// In reply mode, [`feed`](Self::feed) also returns a provisional
154/// [`Revision`] whenever the drawable picture changes. Programs are not drawn
155/// while streaming: in program mode `feed` always returns `None`.
156pub struct StreamParser {
157    mode: Mode,
158    bytes: Vec<u8>,
159    machine: Option<Box<Machine>>,
160    last: Revision,
161    work: Work,
162}
163
164impl StreamParser {
165    pub fn new(mode: Mode) -> Self {
166        Self {
167            mode,
168            bytes: vec![],
169            machine: (mode == Mode::Reply).then(|| Box::new(Machine::new())),
170            last: Revision::default(),
171            work: Work::default(),
172        }
173    }
174
175    /// Accepts the next chunk. Returns the new revision when the provisional
176    /// picture differs from the last one returned, and `None` otherwise.
177    pub fn feed(&mut self, chunk: &[u8]) -> Option<Revision> {
178        let keep = chunk
179            .len()
180            .min((source_cap(self.mode) + 3).saturating_sub(self.bytes.len()));
181        self.bytes.extend_from_slice(&chunk[..keep]);
182        self.work = Work::default();
183        let machine = self.machine.as_mut()?;
184        for &byte in chunk {
185            if machine.stopped {
186                break;
187            }
188            machine.byte(byte);
189        }
190        self.work = std::mem::take(&mut machine.work);
191        if !std::mem::take(&mut machine.out.dirty) {
192            return None;
193        }
194        let revision = machine.out.snapshot();
195        if revision == self.last {
196            return None;
197        }
198        self.last = revision.clone();
199        Some(revision)
200    }
201
202    /// The current provisional picture: the last revision `feed` returned, or
203    /// an empty one before any.
204    pub fn revision(&self) -> &Revision {
205        &self.last
206    }
207
208    /// The work the last `feed` call did.
209    pub fn work(&self) -> Work {
210        self.work
211    }
212
213    /// Parses everything received, exactly as [`parse_bytes`] parses it.
214    pub fn finish(self) -> Document {
215        parse_bytes(&self.bytes, self.mode)
216    }
217}
218
219/// Replaces control characters other than line feed and tab, so provisional
220/// text never carries terminal or layout controls. A carriage return becomes
221/// a line feed; the builder drops the line feed of a CRLF pair.
222fn sanitize(c: char) -> char {
223    match c {
224        '\r' => '\n',
225        '\n' | '\t' => c,
226        c if c.is_control() => '\u{fffd}',
227        c => c,
228    }
229}
230
231fn sanitize_str(text: &str) -> String {
232    let mut out = String::with_capacity(text.len());
233    let mut cr = false;
234    for c in text.chars() {
235        if !(cr && c == '\n') {
236            out.push(sanitize(c));
237        }
238        cr = c == '\r';
239    }
240    out
241}
242
243/// A text run still being written into a frame.
244struct Run {
245    literal: bool,
246    span: Span,
247    text: String,
248    cr: bool,
249}
250
251impl Run {
252    fn node(&self) -> DisplayNode {
253        DisplayNode {
254            span: self.span,
255            kind: if self.literal {
256                Display::Literal(self.text.clone())
257            } else {
258                Display::Text(self.text.clone())
259            },
260            children: vec![],
261        }
262    }
263}
264
265/// An open display container.
266struct Frame {
267    kind: Display,
268    span: Span,
269    children: Vec<Arc<DisplayNode>>,
270    run: Option<Run>,
271}
272
273/// The provisional display tree, built append-only except where a later
274/// byte reclassifies the content of the innermost open frame. Finished nodes
275/// are shared, so a snapshot copies only the open frames' child lists and
276/// the current text run.
277struct Builder {
278    frames: Vec<Frame>,
279    dirty: bool,
280}
281
282impl Builder {
283    fn new() -> Self {
284        Self {
285            frames: vec![Frame {
286                kind: Display::Paragraph,
287                span: Span::default(),
288                children: vec![],
289                run: None,
290            }],
291            dirty: false,
292        }
293    }
294    fn top(&mut self) -> &mut Frame {
295        self.frames.last_mut().expect("the root frame stays open")
296    }
297    fn top_kind(&self) -> &Display {
298        &self.frames.last().expect("the root frame stays open").kind
299    }
300    fn flush_run(&mut self) {
301        let top = self.top();
302        if let Some(run) = top.run.take() {
303            top.children.push(Arc::new(run.node()));
304        }
305    }
306    fn extend(&mut self, end: usize) {
307        let top = self.top();
308        top.span.end = top.span.end.max(end);
309    }
310    fn open(&mut self, kind: Display, at: usize) {
311        self.flush_run();
312        self.frames.push(Frame {
313            kind,
314            span: Span::new(at, at),
315            children: vec![],
316            run: None,
317        });
318        self.dirty = true;
319    }
320    fn close(&mut self) {
321        if self.frames.len() < 2 {
322            return;
323        }
324        self.flush_run();
325        let frame = self.frames.pop().expect("checked above");
326        let node = DisplayNode {
327            span: frame.span,
328            kind: frame.kind,
329            children: frame.children,
330        };
331        self.extend(node.span.end);
332        self.top().children.push(Arc::new(node));
333        self.dirty = true;
334    }
335    /// Drops the innermost frame and everything drawn in it.
336    fn discard(&mut self) {
337        if self.frames.len() > 1 {
338            self.frames.pop();
339            self.dirty = true;
340        }
341    }
342    fn push(&mut self, node: DisplayNode) {
343        self.flush_run();
344        self.extend(node.span.end);
345        self.top().children.push(Arc::new(node));
346        self.dirty = true;
347    }
348    fn char(&mut self, c: char, start: usize, end: usize, literal: bool) {
349        let top = self.top();
350        let same = top
351            .run
352            .as_ref()
353            .is_some_and(|run| run.literal == literal && run.text.len() < RUN_BYTES);
354        if !same {
355            if let Some(run) = top.run.take() {
356                top.children.push(Arc::new(run.node()));
357            }
358            top.run = Some(Run {
359                literal,
360                span: Span::new(start, start),
361                text: String::new(),
362                cr: false,
363            });
364        }
365        let run = top.run.as_mut().expect("opened above");
366        if !(run.cr && c == '\n') {
367            run.text.push(sanitize(c));
368        }
369        run.cr = c == '\r';
370        run.span.end = end;
371        top.span.end = top.span.end.max(end);
372        self.dirty = true;
373    }
374    /// Removes trailing spaces and tabs from the text run being written.
375    fn trim_run(&mut self) {
376        let top = self.top();
377        let Some(run) = &mut top.run else { return };
378        let kept = run.text.trim_end_matches([' ', '\t']).len();
379        if run.literal || kept == run.text.len() {
380            return;
381        }
382        run.span.end -= run.text.len() - kept;
383        run.text.truncate(kept);
384        if run.text.is_empty() {
385            top.run = None;
386        }
387        self.dirty = true;
388    }
389    fn snapshot(&self) -> Revision {
390        let mut nested: Option<DisplayNode> = None;
391        for (i, frame) in self.frames.iter().enumerate().rev() {
392            let mut children = frame.children.clone();
393            let mut end = frame.span.end;
394            if let Some(run) = &frame.run {
395                children.push(Arc::new(run.node()));
396            }
397            if let Some(node) = nested.take() {
398                end = end.max(node.span.end);
399                children.push(Arc::new(node));
400            }
401            if i == 0 {
402                return Revision { nodes: children };
403            }
404            nested = Some(DisplayNode {
405                span: Span::new(frame.span.start, end),
406                kind: frame.kind.clone(),
407                children,
408            });
409        }
410        Revision::default()
411    }
412}
413
414/// Converts a node from the final parser into inert display, mapping its
415/// offsets (relative to `source`) through `map`.
416fn convert(node: &Node, source: &str, map: &dyn Fn(usize) -> usize) -> DisplayNode {
417    let span = Span::new(map(node.span.start), map(node.span.end));
418    let mut children: Vec<Arc<DisplayNode>> = node
419        .children
420        .iter()
421        .map(|child| Arc::new(convert(child, source, map)))
422        .collect();
423    let kind = match &node.kind {
424        Kind::Markdown(markdown) => match markdown {
425            Markdown::Paragraph => Display::Paragraph,
426            Markdown::Heading(level) => Display::Heading(*level),
427            Markdown::ThematicBreak => Display::ThematicBreak,
428            Markdown::CodeBlock { language, text } => {
429                children = vec![Arc::new(DisplayNode {
430                    span,
431                    kind: Display::Text(sanitize_str(text)),
432                    children: vec![],
433                })];
434                Display::CodeBlock {
435                    language: sanitize_str(language),
436                }
437            }
438            Markdown::Code(text) => Display::Code(sanitize_str(text)),
439            Markdown::Quote => Display::Quote,
440            Markdown::List { start } => Display::List { start: *start },
441            Markdown::Item => Display::Item,
442            Markdown::Task(checked) => Display::Task(*checked),
443            Markdown::Table => Display::Table,
444            Markdown::TableHead => Display::TableHead,
445            Markdown::TableRow => Display::TableRow,
446            Markdown::TableCell => Display::TableCell,
447            Markdown::Emphasis => Display::Emphasis,
448            Markdown::Strong => Display::Strong,
449            Markdown::Strikethrough => Display::Strikethrough,
450            Markdown::Link { image, .. } => Display::Link { image: *image },
451            Markdown::SoftBreak => Display::SoftBreak,
452            Markdown::HardBreak => Display::HardBreak,
453        },
454        Kind::Text(text) => Display::Text(sanitize_str(text)),
455        Kind::Literal(text) => Display::Literal(sanitize_str(text)),
456        Kind::Reference { .. } => Display::PendingReference,
457        Kind::Tag { name, block, .. } => Display::Tag {
458            name: name.clone(),
459            block: *block,
460        },
461        // Reply children never hold these; if one appears, draw its source.
462        Kind::Handle { .. }
463        | Kind::Interpolation { .. }
464        | Kind::Declaration { .. }
465        | Kind::Expression(_) => {
466            children.clear();
467            Display::Literal(sanitize_str(
468                source
469                    .get(node.span.start..node.span.end)
470                    .unwrap_or_default(),
471            ))
472        }
473    };
474    DisplayNode {
475        span,
476        kind,
477        children,
478    }
479}
480
481/// Decodes UTF-8 one byte at a time, replacing each maximal invalid
482/// subsequence with U+FFFD as `String::from_utf8_lossy` does.
483#[derive(Default)]
484struct Decoder {
485    buf: [u8; 4],
486    len: usize,
487    need: usize,
488}
489
490impl Decoder {
491    /// Feeds one byte; returns up to two decoded characters, each with the
492    /// count of fed bytes (this one included) it does not cover.
493    fn push(&mut self, byte: u8, out: &mut Vec<(char, usize)>) {
494        if self.len > 0 {
495            let valid = match (self.buf[0], self.len) {
496                (0xE0, 1) => (0xA0..=0xBF).contains(&byte),
497                (0xED, 1) => (0x80..=0x9F).contains(&byte),
498                (0xF0, 1) => (0x90..=0xBF).contains(&byte),
499                (0xF4, 1) => (0x80..=0x8F).contains(&byte),
500                _ => (0x80..=0xBF).contains(&byte),
501            };
502            if valid {
503                self.buf[self.len] = byte;
504                self.len += 1;
505                if self.len == self.need {
506                    let c = std::str::from_utf8(&self.buf[..self.len])
507                        .ok()
508                        .and_then(|s| s.chars().next())
509                        .unwrap_or('\u{fffd}');
510                    self.len = 0;
511                    out.push((c, 0));
512                }
513                return;
514            }
515            self.len = 0;
516            out.push(('\u{fffd}', 1));
517        }
518        let need = match byte {
519            0x00..=0x7F => {
520                out.push((byte as char, 0));
521                return;
522            }
523            0xC2..=0xDF => 2,
524            0xE0..=0xEF => 3,
525            0xF0..=0xF4 => 4,
526            _ => {
527                out.push(('\u{fffd}', 0));
528                return;
529            }
530        };
531        self.buf[0] = byte;
532        self.len = 1;
533        self.need = need;
534    }
535}
536
537/// One decoded character, at its byte offset in the received text. `fresh`
538/// characters have not yet been through line classification; the others are
539/// held inline text being examined again.
540#[derive(Clone, Copy, Debug)]
541struct Item {
542    at: usize,
543    c: char,
544    fresh: bool,
545}
546
547impl Item {
548    fn end(self) -> usize {
549        self.at + self.c.len_utf8()
550    }
551    fn inline(self) -> Self {
552        Self {
553            fresh: false,
554            ..self
555        }
556    }
557}
558
559fn eol(c: char) -> bool {
560    c == '\n' || c == '\r'
561}
562
563fn bytes(items: &[Item]) -> usize {
564    items.iter().map(|item| item.c.len_utf8()).sum()
565}
566
567enum Container {
568    Quote,
569    List { ordered: bool },
570    Item { width: usize },
571}
572
573/// The block whose content the current lines feed.
574#[derive(Default)]
575enum Leaf {
576    #[default]
577    None,
578    Paragraph {
579        /// The paragraph's first line, kept while it may still become a table
580        /// header row.
581        header: Option<Header>,
582        lines: usize,
583    },
584    Heading,
585    Fence {
586        ch: char,
587        len: usize,
588        indent: usize,
589    },
590    Table,
591}
592
593#[derive(Default)]
594struct Header {
595    items: Vec<Item>,
596    bytes: usize,
597    pipe: bool,
598}
599
600enum Line {
601    /// The line's start is still being classified.
602    Start(Box<LineStart>),
603    /// The rest of the line is inline content of the current leaf.
604    Content { skip_ws: bool },
605    /// The rest of the line is fenced code.
606    Code,
607}
608
609impl Line {
610    fn start() -> Self {
611        Self::Start(Box::default())
612    }
613}
614
615#[derive(Default)]
616struct LineStart {
617    /// Open containers this line continues so far.
618    matched: usize,
619    /// Columns of indentation since the last container marker.
620    indent: usize,
621    /// One space after `>` belongs to the quote marker.
622    swallow: bool,
623    /// Containers this line opens.
624    opened: Vec<Opening>,
625    phase: Phase,
626    /// Characters this line has consumed so far.
627    consumed: usize,
628    /// The line was already tried as a table delimiter row.
629    no_delim: bool,
630    /// The line's content starts a reply declaration.
631    declaration: bool,
632}
633
634enum Opening {
635    Quote {
636        at: usize,
637    },
638    Item {
639        at: usize,
640        ordered: bool,
641        start: Option<u64>,
642        width: usize,
643    },
644}
645
646#[derive(Default)]
647enum Phase {
648    #[default]
649    Match,
650    Open,
651    Bullet {
652        ch: char,
653        buf: Vec<Item>,
654        indent: usize,
655    },
656    /// A thematic break so far. `item` is the list marker width when the
657    /// line is a list item if it turns out not to be a break.
658    Thematic {
659        ch: char,
660        count: usize,
661        buf: Vec<Item>,
662        item: Option<usize>,
663    },
664    Ordered {
665        buf: Vec<Item>,
666        value: u64,
667        digits: usize,
668        indent: usize,
669    },
670    OrderedDelim {
671        buf: Vec<Item>,
672        value: u64,
673        width: usize,
674    },
675    ItemSpaces {
676        at: usize,
677        ordered: bool,
678        start: Option<u64>,
679        width: usize,
680        spaces: usize,
681    },
682    Heading {
683        buf: Vec<Item>,
684        level: u8,
685    },
686    FenceRun {
687        buf: Vec<Item>,
688        ch: char,
689        count: usize,
690    },
691    FenceInfo {
692        buf: Vec<Item>,
693        ch: char,
694        count: usize,
695        info: String,
696    },
697    Declaration {
698        buf: Vec<Item>,
699    },
700    DelimRow {
701        buf: Vec<Item>,
702    },
703    FenceIndent {
704        stripped: usize,
705    },
706    FenceClose {
707        buf: Vec<Item>,
708        count: usize,
709        lead: usize,
710        trailing: bool,
711    },
712}
713
714#[derive(Clone, Copy, PartialEq, Eq)]
715enum Target {
716    Paragraph,
717    Heading,
718    Cell,
719}
720
721/// The inline run in progress: a paragraph, a heading or a table cell.
722#[derive(Default)]
723struct Inline {
724    target: Option<Target>,
725    /// The run's frame is open in the builder.
726    opened: bool,
727    pending: Pending,
728    delims: Vec<Delim>,
729    /// The source character before the next one, for emphasis flanking.
730    prev: char,
731    /// Spaces at the end of the run's current line, for hard breaks.
732    spaces: usize,
733    /// Inside a bare `http(s)://` address, drawn as plain text.
734    url: bool,
735    recent: String,
736    /// The next character is the first of a line, at a block position.
737    block_start: bool,
738    /// The next character starts a reply declaration.
739    declaration: bool,
740    /// In a table row: no cell content yet, so a `|` is the leading pipe.
741    leading: bool,
742}
743
744/// Inline text held until its classification is known.
745#[derive(Default)]
746enum Pending {
747    #[default]
748    None,
749    Escape(usize),
750    Bang(usize),
751    Angle {
752        at: usize,
753        block: bool,
754    },
755    Delim {
756        ch: char,
757        at: usize,
758        len: usize,
759    },
760    Reference(Vec<Item>),
761    Ticks {
762        at: usize,
763        len: usize,
764    },
765    Code(Code),
766    Link(Link),
767}
768
769struct Code {
770    at: usize,
771    run: usize,
772    buf: Vec<Item>,
773    closing: usize,
774    bytes: usize,
775}
776
777struct Link {
778    image: bool,
779    buf: Vec<Item>,
780    bytes: usize,
781    stage: Stage,
782    escaped: bool,
783}
784
785enum Stage {
786    Label { depth: usize },
787    Paren,
788    Destination { nesting: usize, quote: Option<char> },
789}
790
791/// An emphasis delimiter run that may still open emphasis. `index` is its
792/// text node among the run frame's children.
793struct Delim {
794    ch: char,
795    count: usize,
796    orig: usize,
797    close: bool,
798    index: usize,
799    start: usize,
800    end: usize,
801}
802
803/// Text taken verbatim, bypassing line classification.
804enum Raw {
805    /// Reply prose braces: opaque literal through the matching `}`.
806    Braces(Braces),
807    /// A reply declaration: opaque literal through its last line.
808    Declaration(Declaration),
809    /// A tag, held whole until it is complete.
810    Capture(Box<Capture>),
811}
812
813struct Braces {
814    depth: usize,
815    escaped: bool,
816    cr: bool,
817    /// Whitespace at the start of a new line, held until the line proves
818    /// blank (which ends the braces) or not.
819    held: Option<Vec<Item>>,
820}
821
822struct Declaration {
823    scan: DeclarationScan,
824    cr: bool,
825    /// The line break and the start of the next line, held until it shows
826    /// whether the declaration continues.
827    held: Option<(Vec<Item>, String)>,
828}
829
830struct Machine {
831    decoder: Decoder,
832    decoded: Vec<(char, usize)>,
833    raw: usize,
834    offset: usize,
835    stopped: bool,
836    queue: VecDeque<Item>,
837    out: Builder,
838    containers: Vec<Container>,
839    leaf: Leaf,
840    line: Line,
841    /// The end of the paragraph's last line, until the next line shows
842    /// whether the paragraph continues.
843    eol: Option<usize>,
844    cr: bool,
845    inline: Inline,
846    raw_mode: Option<Raw>,
847    work: Work,
848}
849
850impl Machine {
851    fn new() -> Self {
852        Self {
853            decoder: Decoder::default(),
854            decoded: vec![],
855            raw: 0,
856            offset: 0,
857            stopped: false,
858            queue: VecDeque::new(),
859            out: Builder::new(),
860            containers: vec![],
861            leaf: Leaf::None,
862            line: Line::start(),
863            eol: None,
864            cr: false,
865            inline: Inline::default(),
866            raw_mode: None,
867            work: Work::default(),
868        }
869    }
870
871    fn byte(&mut self, byte: u8) {
872        self.work.scanned += 1;
873        self.raw += 1;
874        let mut decoded = std::mem::take(&mut self.decoded);
875        self.decoder.push(byte, &mut decoded);
876        for (c, uncovered) in decoded.drain(..) {
877            // The final parse keeps no character that ends past the limit.
878            if self.raw - uncovered > crate::REPLY_BYTES {
879                self.stopped = true;
880                break;
881            }
882            self.char(c);
883        }
884        self.decoded = decoded;
885        if self.raw >= crate::REPLY_BYTES + 3 {
886            self.stopped = true;
887        }
888    }
889
890    fn char(&mut self, c: char) {
891        let at = self.offset;
892        self.offset += c.len_utf8();
893        if at == 0 && c == '\u{feff}' {
894            return;
895        }
896        let mut queue = std::mem::take(&mut self.queue);
897        queue.push_back(Item { at, c, fresh: true });
898        while let Some(item) = queue.pop_front() {
899            self.step(item, &mut queue);
900        }
901        self.queue = queue;
902    }
903
904    fn step(&mut self, item: Item, q: &mut VecDeque<Item>) {
905        if self.raw_mode.is_some() {
906            self.raw_step(item, q);
907        } else if item.fresh {
908            self.block_step(item, q);
909        } else {
910            self.inline_step(item, q);
911        }
912    }
913
914    /// Queues held characters to be examined again, ahead of anything else.
915    fn rerun(&mut self, q: &mut VecDeque<Item>, items: &[Item]) {
916        for item in items.iter().rev() {
917            self.work.reexamined += item.c.len_utf8();
918            q.push_front(*item);
919        }
920    }
921
922    /// Examines held inline characters again now, to completion, outside the
923    /// line machine. Used when a block ends and its held text must resolve.
924    fn run_now(&mut self, items: &[Item]) {
925        let mut q = VecDeque::new();
926        self.rerun(&mut q, items);
927        while let Some(item) = q.pop_front() {
928            if self.raw_mode.is_some() {
929                self.raw_step(item, &mut q);
930            } else {
931                self.inline_step(item.inline(), &mut q);
932            }
933        }
934    }
935}
936
937/// What a line start resolved to, when it was not inline content.
938enum Block {
939    Heading {
940        at: usize,
941        level: u8,
942    },
943    Thematic {
944        span: Span,
945    },
946    Fence {
947        at: usize,
948        ch: char,
949        len: usize,
950        indent: usize,
951        language: String,
952    },
953}
954
955impl Machine {
956    fn block_step(&mut self, item: Item, q: &mut VecDeque<Item>) {
957        let c = item.c;
958        let crlf = c == '\n' && self.cr;
959        self.cr = c == '\r';
960        if crlf {
961            return;
962        }
963        match &mut self.line {
964            Line::Content { skip_ws } => {
965                if eol(c) {
966                    self.content_eol(item.at);
967                    return;
968                }
969                if *skip_ws && (c == ' ' || c == '\t') {
970                    return;
971                }
972                *skip_ws = false;
973                if let Leaf::Paragraph {
974                    header: Some(header),
975                    lines: 1,
976                } = &mut self.leaf
977                {
978                    header.items.push(item);
979                    header.bytes += c.len_utf8();
980                    header.pipe |= c == '|';
981                    if header.bytes > STREAM_CONTEXT_BYTES {
982                        self.drop_header();
983                    }
984                }
985                self.inline_step(item.inline(), q);
986            }
987            Line::Code => {
988                self.out.char(c, item.at, item.end(), false);
989                if eol(c) {
990                    self.out.flush_run();
991                    self.line = Line::start();
992                }
993            }
994            Line::Start(_) => self.line_char(item, q),
995        }
996    }
997
998    fn drop_header(&mut self) {
999        if let Leaf::Paragraph { header, .. } = &mut self.leaf {
1000            *header = None;
1001        }
1002    }
1003
1004    /// A line of inline content ends.
1005    fn content_eol(&mut self, at: usize) {
1006        match &mut self.leaf {
1007            Leaf::Paragraph { header, .. } => {
1008                if header.as_ref().is_some_and(|header| !header.pipe) {
1009                    *header = None;
1010                }
1011                self.eol = Some(at);
1012            }
1013            Leaf::Heading => {
1014                self.end_run();
1015                self.leaf = Leaf::None;
1016            }
1017            Leaf::Table => {
1018                self.end_run();
1019                self.out.close();
1020            }
1021            Leaf::None | Leaf::Fence { .. } => {}
1022        }
1023        self.line = Line::start();
1024    }
1025
1026    /// Classifies the start of a line, one character at a time.
1027    fn line_char(&mut self, item: Item, q: &mut VecDeque<Item>) {
1028        let Line::Start(mut ls) = std::mem::replace(&mut self.line, Line::Code) else {
1029            return;
1030        };
1031        let decided = self.classify(&mut ls, item, q);
1032        if !decided {
1033            ls.consumed += 1;
1034            self.line = Line::Start(ls);
1035        }
1036    }
1037
1038    /// Returns true when the line start is decided and `self.line` is set.
1039    fn classify(&mut self, ls: &mut LineStart, item: Item, q: &mut VecDeque<Item>) -> bool {
1040        let c = item.c;
1041        loop {
1042            if ls.swallow {
1043                ls.swallow = false;
1044                if c == ' ' {
1045                    return false;
1046                }
1047            }
1048            match &mut ls.phase {
1049                Phase::Match => {
1050                    while matches!(
1051                        self.containers.get(ls.matched),
1052                        Some(Container::List { .. })
1053                    ) {
1054                        ls.matched += 1;
1055                    }
1056                    if ls.matched == self.containers.len() {
1057                        ls.phase = if matches!(self.leaf, Leaf::Fence { .. }) {
1058                            Phase::FenceIndent { stripped: 0 }
1059                        } else {
1060                            Phase::Open
1061                        };
1062                        continue;
1063                    }
1064                    if eol(c) {
1065                        return self.blank_line(ls, item);
1066                    }
1067                    let fits = match self.containers[ls.matched] {
1068                        Container::Quote => {
1069                            if c == '>' && ls.indent <= 3 {
1070                                ls.matched += 1;
1071                                ls.indent = 0;
1072                                ls.swallow = true;
1073                                return false;
1074                            }
1075                            (c == ' ' || c == '\t') && ls.indent < 3
1076                        }
1077                        Container::Item { width } => {
1078                            if c == ' ' || c == '\t' {
1079                                ls.indent += if c == '\t' { 4 - ls.indent % 4 } else { 1 };
1080                                if ls.indent >= width {
1081                                    ls.indent -= width;
1082                                    ls.matched += 1;
1083                                }
1084                                return false;
1085                            }
1086                            false
1087                        }
1088                        Container::List { .. } => unreachable!("lists match through their items"),
1089                    };
1090                    if fits {
1091                        ls.indent += if c == '\t' { 4 - ls.indent % 4 } else { 1 };
1092                        return false;
1093                    }
1094                    // The line leaves an open container. Fenced code has no
1095                    // lazy continuation, so it ends here.
1096                    if matches!(self.leaf, Leaf::Fence { .. }) {
1097                        self.close_containers(ls.matched);
1098                    }
1099                    ls.phase = Phase::Open;
1100                }
1101                Phase::Open => {
1102                    if c == ' ' || c == '\t' {
1103                        ls.indent += if c == '\t' { 4 - ls.indent % 4 } else { 1 };
1104                        return false;
1105                    }
1106                    if eol(c) {
1107                        return self.blank_line(ls, item);
1108                    }
1109                    let header = matches!(
1110                        &self.leaf,
1111                        Leaf::Paragraph { header: Some(h), lines: 1 } if h.pipe
1112                    );
1113                    if header
1114                        && !ls.no_delim
1115                        && ls.opened.is_empty()
1116                        && ls.matched == self.containers.len()
1117                        && matches!(c, '|' | '-' | ':')
1118                    {
1119                        ls.phase = Phase::DelimRow { buf: vec![item] };
1120                        return false;
1121                    }
1122                    if ls.indent >= 4 {
1123                        return self.content(ls, &[item], q);
1124                    }
1125                    let buf = vec![item];
1126                    let indent = ls.indent;
1127                    ls.phase = match c {
1128                        '>' => {
1129                            ls.opened.push(Opening::Quote { at: item.at });
1130                            ls.indent = 0;
1131                            ls.swallow = true;
1132                            return false;
1133                        }
1134                        '-' | '+' | '*' => Phase::Bullet { ch: c, buf, indent },
1135                        '_' => Phase::Thematic {
1136                            ch: c,
1137                            count: 1,
1138                            buf,
1139                            item: None,
1140                        },
1141                        '0'..='9' => Phase::Ordered {
1142                            buf,
1143                            value: u64::from(c as u8 - b'0'),
1144                            digits: 1,
1145                            indent,
1146                        },
1147                        '#' => Phase::Heading { buf, level: 1 },
1148                        '`' | '~' => Phase::FenceRun {
1149                            buf,
1150                            ch: c,
1151                            count: 1,
1152                        },
1153                        'e' if ls.consumed == 0 => Phase::Declaration { buf },
1154                        _ => return self.content(ls, &buf, q),
1155                    };
1156                    return false;
1157                }
1158                Phase::Bullet { ch, buf, indent } => {
1159                    let (ch, indent) = (*ch, *indent);
1160                    if c == ' ' || c == '\t' || eol(c) {
1161                        if ch == '+' {
1162                            ls.phase = Phase::ItemSpaces {
1163                                at: buf[0].at,
1164                                ordered: false,
1165                                start: None,
1166                                width: indent + 1,
1167                                spaces: 0,
1168                            };
1169                        } else {
1170                            ls.phase = Phase::Thematic {
1171                                ch,
1172                                count: 1,
1173                                buf: std::mem::take(buf),
1174                                item: Some(indent + 1),
1175                            };
1176                        }
1177                        continue;
1178                    }
1179                    if c == ch && ch != '+' {
1180                        buf.push(item);
1181                        ls.phase = Phase::Thematic {
1182                            ch,
1183                            count: 2,
1184                            buf: std::mem::take(buf),
1185                            item: None,
1186                        };
1187                        return false;
1188                    }
1189                    let mut buf = std::mem::take(buf);
1190                    buf.push(item);
1191                    return self.content(ls, &buf, q);
1192                }
1193                Phase::Thematic {
1194                    ch,
1195                    count,
1196                    buf,
1197                    item: marker,
1198                } => {
1199                    if c == *ch || c == ' ' || c == '\t' {
1200                        // Marks and blanks are ASCII, so the length is in bytes.
1201                        if buf.len() >= STREAM_CONTEXT_BYTES {
1202                            let mut buf = std::mem::take(buf);
1203                            buf.push(item);
1204                            return self.content_with(ls, &buf, q, true);
1205                        }
1206                        *count += usize::from(c == *ch);
1207                        buf.push(item);
1208                        return false;
1209                    }
1210                    if eol(c) && *count >= 3 {
1211                        let span = Span::new(buf[0].at, item.at);
1212                        return self.block(ls, Block::Thematic { span }, Some(item), q);
1213                    }
1214                    let mut buf = std::mem::take(buf);
1215                    buf.push(item);
1216                    if let Some(width) = *marker {
1217                        // Not a break: a list item whose content follows the
1218                        // marker.
1219                        ls.phase = Phase::ItemSpaces {
1220                            at: buf[0].at,
1221                            ordered: false,
1222                            start: None,
1223                            width,
1224                            spaces: 0,
1225                        };
1226                        self.rerun(q, &buf[1..]);
1227                        return false;
1228                    }
1229                    return self.content(ls, &buf, q);
1230                }
1231                Phase::Ordered {
1232                    buf,
1233                    value,
1234                    digits,
1235                    indent,
1236                } => {
1237                    if c.is_ascii_digit() && *digits < 9 {
1238                        *value = *value * 10 + u64::from(c as u8 - b'0');
1239                        *digits += 1;
1240                        buf.push(item);
1241                        return false;
1242                    }
1243                    let mut buf = std::mem::take(buf);
1244                    buf.push(item);
1245                    if c == '.' || c == ')' {
1246                        ls.phase = Phase::OrderedDelim {
1247                            width: *indent + *digits + 1,
1248                            value: *value,
1249                            buf,
1250                        };
1251                        return false;
1252                    }
1253                    return self.content(ls, &buf, q);
1254                }
1255                Phase::OrderedDelim { buf, value, width } => {
1256                    if c == ' ' || c == '\t' || eol(c) {
1257                        ls.phase = Phase::ItemSpaces {
1258                            at: buf[0].at,
1259                            ordered: true,
1260                            start: Some(*value),
1261                            width: *width,
1262                            spaces: 0,
1263                        };
1264                        continue;
1265                    }
1266                    let mut buf = std::mem::take(buf);
1267                    buf.push(item);
1268                    return self.content(ls, &buf, q);
1269                }
1270                Phase::ItemSpaces {
1271                    at,
1272                    ordered,
1273                    start,
1274                    width,
1275                    spaces,
1276                } => {
1277                    if c == ' ' || c == '\t' {
1278                        *spaces += if c == '\t' {
1279                            4 - (*width + *spaces) % 4
1280                        } else {
1281                            1
1282                        };
1283                        return false;
1284                    }
1285                    let content_spaces = if *spaces == 0 || *spaces > 4 {
1286                        1
1287                    } else {
1288                        *spaces
1289                    };
1290                    ls.indent = spaces.saturating_sub(content_spaces);
1291                    ls.opened.push(Opening::Item {
1292                        at: *at,
1293                        ordered: *ordered,
1294                        start: *start,
1295                        width: *width + content_spaces,
1296                    });
1297                    ls.phase = Phase::Open;
1298                }
1299                Phase::Heading { buf, level } => {
1300                    if c == '#' && *level < 6 {
1301                        *level += 1;
1302                        buf.push(item);
1303                        return false;
1304                    }
1305                    if c == ' ' || c == '\t' || eol(c) {
1306                        let block = Block::Heading {
1307                            at: buf[0].at,
1308                            level: *level,
1309                        };
1310                        return self.block(ls, block, eol(c).then_some(item), q);
1311                    }
1312                    let mut buf = std::mem::take(buf);
1313                    buf.push(item);
1314                    return self.content(ls, &buf, q);
1315                }
1316                Phase::FenceRun { buf, ch, count } => {
1317                    if c == *ch {
1318                        *count += 1;
1319                        buf.push(item);
1320                        return false;
1321                    }
1322                    if *count < 3 {
1323                        let mut buf = std::mem::take(buf);
1324                        buf.push(item);
1325                        return self.content(ls, &buf, q);
1326                    }
1327                    ls.phase = Phase::FenceInfo {
1328                        buf: std::mem::take(buf),
1329                        ch: *ch,
1330                        count: *count,
1331                        info: String::new(),
1332                    };
1333                }
1334                Phase::FenceInfo {
1335                    buf,
1336                    ch,
1337                    count,
1338                    info,
1339                } => {
1340                    if eol(c) {
1341                        let block = Block::Fence {
1342                            at: buf[0].at,
1343                            ch: *ch,
1344                            len: *count,
1345                            indent: ls.indent,
1346                            language: info.split_whitespace().next().unwrap_or("").to_owned(),
1347                        };
1348                        return self.block(ls, block, None, q);
1349                    }
1350                    buf.push(item);
1351                    // A backtick fence's info string has no backticks; a held
1352                    // line stops at the context cap.
1353                    if *ch == '`' && c == '`' {
1354                        let buf = std::mem::take(buf);
1355                        return self.content(ls, &buf, q);
1356                    }
1357                    if info.len() >= STREAM_CONTEXT_BYTES {
1358                        let buf = std::mem::take(buf);
1359                        return self.content_with(ls, &buf, q, true);
1360                    }
1361                    info.push(c);
1362                    return false;
1363                }
1364                Phase::Declaration { buf } => {
1365                    let mut text: String = buf.iter().map(|item| item.c).collect();
1366                    text.push(c);
1367                    let mut buf = std::mem::take(buf);
1368                    buf.push(item);
1369                    match declaration_prefix(&text) {
1370                        Some(true) => {
1371                            ls.declaration = true;
1372                            return self.content(ls, &buf, q);
1373                        }
1374                        Some(false) => {
1375                            ls.phase = Phase::Declaration { buf };
1376                            return false;
1377                        }
1378                        None => return self.content(ls, &buf, q),
1379                    }
1380                }
1381                Phase::DelimRow { buf } => {
1382                    if matches!(c, '|' | '-' | ':' | ' ' | '\t') && buf.len() < STREAM_CONTEXT_BYTES
1383                    {
1384                        buf.push(item);
1385                        return false;
1386                    }
1387                    let mut buf = std::mem::take(buf);
1388                    if eol(c) && self.delimiter_row(&buf) {
1389                        self.make_table(&buf);
1390                        self.line = Line::start();
1391                        return true;
1392                    }
1393                    // Not a delimiter row: classify the line again as usual.
1394                    ls.no_delim = true;
1395                    ls.phase = Phase::Open;
1396                    buf.push(item);
1397                    self.rerun(q, &buf);
1398                    return false;
1399                }
1400                Phase::FenceIndent { stripped } => {
1401                    let indent = match self.leaf {
1402                        Leaf::Fence { indent, .. } => indent,
1403                        _ => 0,
1404                    };
1405                    if c == ' ' && *stripped < indent {
1406                        *stripped += 1;
1407                        return false;
1408                    }
1409                    ls.phase = Phase::FenceClose {
1410                        buf: vec![],
1411                        count: 0,
1412                        lead: 0,
1413                        trailing: false,
1414                    };
1415                }
1416                Phase::FenceClose {
1417                    buf,
1418                    count,
1419                    lead,
1420                    trailing,
1421                } => {
1422                    let (ch, len) = match self.leaf {
1423                        Leaf::Fence { ch, len, .. } => (ch, len),
1424                        _ => ('`', usize::MAX),
1425                    };
1426                    if eol(c) && *count >= len {
1427                        self.close_leaf();
1428                        self.line = Line::start();
1429                        return true;
1430                    }
1431                    let held = if c == ' ' && *count == 0 && *lead < 3 {
1432                        *lead += 1;
1433                        true
1434                    } else if c == ch && !*trailing {
1435                        *count += 1;
1436                        true
1437                    } else if (c == ' ' || c == '\t') && *count > 0 {
1438                        *trailing = true;
1439                        true
1440                    } else {
1441                        false
1442                    };
1443                    if held && !eol(c) {
1444                        buf.push(item);
1445                        return false;
1446                    }
1447                    let mut buf = std::mem::take(buf);
1448                    buf.push(item);
1449                    self.line = Line::Code;
1450                    self.rerun(q, &buf);
1451                    return true;
1452                }
1453            }
1454        }
1455    }
1456
1457    /// The line is a blank line (only whitespace and container markers).
1458    fn blank_line(&mut self, ls: &mut LineStart, item: Item) -> bool {
1459        // A blank line continues open list items but not quotes.
1460        let mut keep = ls.matched;
1461        while keep < self.containers.len() && !matches!(self.containers[keep], Container::Quote) {
1462            keep += 1;
1463        }
1464        if ls.opened.is_empty()
1465            && keep == self.containers.len()
1466            && matches!(self.leaf, Leaf::Fence { .. })
1467        {
1468            self.out.char('\n', item.at, item.end(), false);
1469            self.out.flush_run();
1470        } else if ls.opened.is_empty() {
1471            self.close_containers(keep);
1472        } else {
1473            self.settle(ls, false);
1474        }
1475        self.line = Line::start();
1476        true
1477    }
1478
1479    /// Closes and opens containers for a line that is not a paragraph
1480    /// continuation. Returns true when the line continues the open paragraph.
1481    fn settle(&mut self, ls: &mut LineStart, content: bool) -> bool {
1482        if content && ls.opened.is_empty() && matches!(self.leaf, Leaf::Paragraph { .. }) {
1483            return true;
1484        }
1485        let mut keep = ls.matched;
1486        if keep > 0 {
1487            if let Container::List { ordered } = self.containers[keep - 1] {
1488                let same = matches!(ls.opened.first(), Some(Opening::Item { ordered: o, .. }) if *o == ordered);
1489                if !same {
1490                    keep -= 1;
1491                }
1492            }
1493        }
1494        self.close_containers(keep);
1495        for opening in std::mem::take(&mut ls.opened) {
1496            if self.containers.len() + 2 > CONTAINERS {
1497                break;
1498            }
1499            match opening {
1500                Opening::Quote { at } => {
1501                    self.containers.push(Container::Quote);
1502                    self.out.open(Display::Quote, at);
1503                }
1504                Opening::Item {
1505                    at,
1506                    ordered,
1507                    start,
1508                    width,
1509                } => {
1510                    let reuse = matches!(self.containers.last(), Some(Container::List { ordered: o }) if *o == ordered);
1511                    if !reuse {
1512                        if matches!(self.containers.last(), Some(Container::List { .. })) {
1513                            self.containers.pop();
1514                            self.out.close();
1515                        }
1516                        self.containers.push(Container::List { ordered });
1517                        self.out.open(Display::List { start }, at);
1518                    }
1519                    self.containers.push(Container::Item { width });
1520                    self.out.open(Display::Item, at);
1521                }
1522            }
1523        }
1524        false
1525    }
1526
1527    fn close_leaf(&mut self) {
1528        match std::mem::take(&mut self.leaf) {
1529            Leaf::Paragraph { .. } | Leaf::Heading => self.end_run(),
1530            Leaf::Fence { .. } => self.out.close(),
1531            Leaf::Table => {
1532                self.end_run();
1533                while matches!(
1534                    self.out.top_kind(),
1535                    Display::TableRow | Display::TableHead | Display::TableCell
1536                ) {
1537                    self.out.close();
1538                }
1539                self.out.close();
1540            }
1541            Leaf::None => {}
1542        }
1543        self.eol = None;
1544    }
1545
1546    fn close_containers(&mut self, keep: usize) {
1547        self.close_leaf();
1548        while self.containers.len() > keep {
1549            self.containers.pop();
1550            self.out.close();
1551        }
1552    }
1553
1554    /// The line is inline content; `items` are its characters so far.
1555    fn content(&mut self, ls: &mut LineStart, items: &[Item], q: &mut VecDeque<Item>) -> bool {
1556        self.content_with(ls, items, q, false)
1557    }
1558
1559    /// Like [`Self::content`]; when `capped`, the held line start reached its
1560    /// cap and is drawn as a provisional literal instead of examined again.
1561    fn content_with(
1562        &mut self,
1563        ls: &mut LineStart,
1564        items: &[Item],
1565        q: &mut VecDeque<Item>,
1566        capped: bool,
1567    ) -> bool {
1568        let block_start = ls.indent <= 3 && !capped;
1569        let row = ls.opened.is_empty()
1570            && ls.matched == self.containers.len()
1571            && matches!(self.leaf, Leaf::Table);
1572        self.line = Line::Content { skip_ws: false };
1573        let mut newline = None;
1574        if row {
1575            self.out.open(Display::TableRow, items[0].at);
1576            self.start_run(Target::Cell);
1577        } else if self.settle(ls, true) {
1578            if let Leaf::Paragraph { header, lines } = &mut self.leaf {
1579                *lines += 1;
1580                *header = None;
1581            }
1582            newline = self.eol.take().map(|at| Item {
1583                at,
1584                c: '\n',
1585                fresh: false,
1586            });
1587        } else {
1588            self.leaf = Leaf::Paragraph {
1589                header: Some(Header::default()),
1590                lines: 1,
1591            };
1592            self.start_run(Target::Paragraph);
1593        }
1594        self.inline.block_start = block_start && !row;
1595        self.inline.declaration = ls.declaration;
1596        if capped {
1597            if let Some(newline) = newline {
1598                self.run_now(&[newline]);
1599            }
1600            self.drop_header();
1601            self.cap_flush(items, "", items[0].at);
1602        } else {
1603            // Both go to the front of the queue: the line's characters first,
1604            // then the paragraph's line break ahead of them, so the break is
1605            // examined before the characters that follow it.
1606            self.rerun(q, items);
1607            if let Some(newline) = newline {
1608                q.push_front(newline);
1609            }
1610        }
1611        true
1612    }
1613
1614    /// The line starts a block that is not a paragraph.
1615    fn block(
1616        &mut self,
1617        ls: &mut LineStart,
1618        block: Block,
1619        eol_item: Option<Item>,
1620        q: &mut VecDeque<Item>,
1621    ) -> bool {
1622        self.settle(ls, false);
1623        match block {
1624            Block::Thematic { span } => {
1625                self.out.push(DisplayNode {
1626                    span,
1627                    kind: Display::ThematicBreak,
1628                    children: vec![],
1629                });
1630                self.line = Line::start();
1631            }
1632            Block::Heading { at, level } => {
1633                self.out.open(Display::Heading(level), at);
1634                self.leaf = Leaf::Heading;
1635                self.start_run(Target::Heading);
1636                self.inline.opened = true;
1637                self.line = Line::Content { skip_ws: true };
1638                if let Some(item) = eol_item {
1639                    self.rerun(q, &[item]);
1640                }
1641            }
1642            Block::Fence {
1643                at,
1644                ch,
1645                len,
1646                indent,
1647                language,
1648            } => {
1649                self.out.open(
1650                    Display::CodeBlock {
1651                        language: sanitize_str(&language),
1652                    },
1653                    at,
1654                );
1655                self.leaf = Leaf::Fence { ch, len, indent };
1656                self.line = Line::start();
1657            }
1658        }
1659        true
1660    }
1661
1662    /// Whether a held line is a GFM delimiter row matching the held header.
1663    fn delimiter_row(&self, buf: &[Item]) -> bool {
1664        let Leaf::Paragraph {
1665            header: Some(header),
1666            ..
1667        } = &self.leaf
1668        else {
1669            return false;
1670        };
1671        let row: String = buf.iter().map(|item| item.c).collect();
1672        let head: String = header.items.iter().map(|item| item.c).collect();
1673        let cells = row_cells(&row);
1674        !cells.is_empty()
1675            && cells.len() == row_cells(&head).len()
1676            && cells.iter().all(|cell| {
1677                let cell = cell.trim();
1678                let dashes = cell.trim_start_matches(':').trim_end_matches(':');
1679                !dashes.is_empty()
1680                    && dashes.bytes().all(|b| b == b'-')
1681                    && cell.len() - dashes.len() <= 2
1682            })
1683    }
1684
1685    /// Redraws the held paragraph line as a table header.
1686    fn make_table(&mut self, delimiter: &[Item]) {
1687        let header = match &mut self.leaf {
1688            Leaf::Paragraph { header, .. } => header.take().unwrap_or_default(),
1689            _ => Header::default(),
1690        };
1691        self.discard_run();
1692        self.leaf = Leaf::Table;
1693        let at = header.items.first().map_or(delimiter[0].at, |item| item.at);
1694        self.out.open(Display::Table, at);
1695        self.out.open(Display::TableHead, at);
1696        self.start_run(Target::Cell);
1697        self.run_now(&header.items);
1698        self.finish_raw();
1699        self.end_run();
1700        self.out.close();
1701    }
1702}
1703
1704/// Splits a table row into cells at unescaped pipes, ignoring one leading
1705/// and one trailing pipe.
1706fn row_cells(row: &str) -> Vec<String> {
1707    let row = row.trim();
1708    let row = row.strip_prefix('|').unwrap_or(row);
1709    let row = match row.strip_suffix('|') {
1710        Some(rest) if !rest.ends_with('\\') => rest,
1711        _ => row,
1712    };
1713    let mut cells = vec![String::new()];
1714    let mut escaped = false;
1715    for c in row.chars() {
1716        if c == '|' && !escaped {
1717            cells.push(String::new());
1718        } else {
1719            cells.last_mut().expect("starts non-empty").push(c);
1720        }
1721        escaped = c == '\\' && !escaped;
1722    }
1723    cells
1724}
1725
1726/// Whether `text` is a reply declaration's start (`export`, whitespace,
1727/// `let` or `const`, whitespace): `Some(true)` when it is, `Some(false)` when
1728/// it still may be, `None` when it cannot be.
1729fn declaration_prefix(text: &str) -> Option<bool> {
1730    if text.len() > 64 {
1731        return None;
1732    }
1733    let Some(rest) = text.strip_prefix("export") else {
1734        return "export".starts_with(text).then_some(false);
1735    };
1736    if rest.is_empty() {
1737        return Some(false);
1738    }
1739    if !rest.starts_with([' ', '\t']) {
1740        return None;
1741    }
1742    let rest = rest.trim_start_matches([' ', '\t']);
1743    for keyword in ["let", "const"] {
1744        if let Some(tail) = rest.strip_prefix(keyword) {
1745            return match tail.chars().next() {
1746                None => Some(false),
1747                Some(' ' | '\t') => Some(true),
1748                Some(_) => None,
1749            };
1750        }
1751        if keyword.starts_with(rest) {
1752            return Some(false);
1753        }
1754    }
1755    None
1756}
1757
1758fn is_punctuation(c: char) -> bool {
1759    c.is_ascii_punctuation() || (!c.is_ascii() && !c.is_alphanumeric() && !c.is_whitespace())
1760}
1761
1762/// A provisional literal for held text, control characters replaced.
1763fn literal_node(items: &[Item], prefix: &str, at: usize) -> DisplayNode {
1764    let mut text = String::from(prefix);
1765    text.extend(items.iter().map(|item| item.c));
1766    DisplayNode {
1767        span: Span::new(
1768            at,
1769            items.last().map_or(at + prefix.len(), |item| item.end()),
1770        ),
1771        kind: Display::Literal(sanitize_str(&text)),
1772        children: vec![],
1773    }
1774}
1775
1776impl Machine {
1777    fn start_run(&mut self, target: Target) {
1778        self.inline = Inline {
1779            target: Some(target),
1780            prev: ' ',
1781            leading: target == Target::Cell,
1782            ..Inline::default()
1783        };
1784    }
1785
1786    /// Opens the run's frame before its first drawn content.
1787    fn ensure_frame(&mut self, at: usize) {
1788        if self.inline.opened {
1789            return;
1790        }
1791        let kind = match self.inline.target {
1792            Some(Target::Cell) => Display::TableCell,
1793            Some(Target::Heading) => Display::Heading(1),
1794            Some(Target::Paragraph) => Display::Paragraph,
1795            None => {
1796                // The inline run already ended, yet inline text arrives on
1797                // the same line: the rest of a line after a block tag ended
1798                // the paragraph, or held text the final parser left
1799                // unconsumed after a tag. That text starts a new paragraph.
1800                self.leaf = Leaf::Paragraph {
1801                    header: None,
1802                    lines: 1,
1803                };
1804                self.inline.target = Some(Target::Paragraph);
1805                Display::Paragraph
1806            }
1807        };
1808        self.out.open(kind, at);
1809        self.inline.opened = true;
1810    }
1811
1812    fn emit_char(&mut self, c: char, start: usize, end: usize) {
1813        self.ensure_frame(start);
1814        self.out.char(c, start, end, false);
1815        let inline = &mut self.inline;
1816        inline.prev = c;
1817        inline.spaces = if c == ' ' { inline.spaces + 1 } else { 0 };
1818        inline.recent.push(c);
1819        if inline.recent.len() > 8 {
1820            inline.recent.remove(0);
1821        }
1822        if inline.recent.ends_with("http://") || inline.recent.ends_with("https://") {
1823            inline.url = true;
1824        }
1825    }
1826
1827    fn emit_items(&mut self, items: &[Item]) {
1828        for item in items {
1829            self.emit_char(item.c, item.at, item.end());
1830        }
1831    }
1832
1833    fn literal_char(&mut self, item: Item) {
1834        self.ensure_frame(item.at);
1835        self.out.char(item.c, item.at, item.end(), true);
1836        self.inline.prev = item.c;
1837        self.inline.spaces = 0;
1838        self.inline.recent.clear();
1839    }
1840
1841    fn emit_node(&mut self, node: DisplayNode, prev: char) {
1842        self.ensure_frame(node.span.start);
1843        self.out.push(node);
1844        self.inline.prev = prev;
1845        self.inline.spaces = 0;
1846        self.inline.recent.clear();
1847    }
1848
1849    /// Draws held text as a provisional literal because its context reached a
1850    /// buffering cap, and forgets the context.
1851    fn cap_flush(&mut self, items: &[Item], prefix: &str, at: usize) {
1852        self.work.reexamined += bytes(items) + prefix.len();
1853        let prev = items.last().map_or(' ', |item| item.c);
1854        self.emit_node(literal_node(items, prefix, at), prev);
1855    }
1856
1857    /// Ends the inline run: held text resolves as the block ends.
1858    fn end_run(&mut self) {
1859        let Some(target) = self.inline.target else {
1860            return;
1861        };
1862        let pending = std::mem::take(&mut self.inline.pending);
1863        let again = self.resolve_end(pending);
1864        if !again.is_empty() {
1865            self.run_now(&again);
1866        }
1867        // Anything held again by that pass is drawn as text, without another.
1868        let pending = std::mem::take(&mut self.inline.pending);
1869        self.flush_pending(pending);
1870        self.finish_raw();
1871        if self.inline.opened {
1872            self.out.trim_run();
1873            if target == Target::Heading {
1874                self.trim_closing_hashes();
1875            }
1876            self.out.close();
1877        }
1878        self.inline = Inline::default();
1879    }
1880
1881    /// Drops the inline run and everything it drew.
1882    fn discard_run(&mut self) {
1883        if self.inline.opened {
1884            self.out.discard();
1885        }
1886        self.inline = Inline::default();
1887        self.eol = None;
1888    }
1889
1890    /// A heading's optional closing sequence of `#` is not drawn.
1891    fn trim_closing_hashes(&mut self) {
1892        let top = self.out.top();
1893        let Some(run) = &mut top.run else { return };
1894        let kept = run.text.trim_end_matches('#');
1895        if kept.len() < run.text.len() && (kept.is_empty() || kept.ends_with([' ', '\t'])) {
1896            let kept = kept.trim_end_matches([' ', '\t']).len();
1897            run.span.end -= run.text.len() - kept;
1898            run.text.truncate(kept);
1899            if run.text.is_empty() {
1900                top.run = None;
1901            }
1902            self.out.dirty = true;
1903        }
1904    }
1905
1906    /// Resolves a held context at the end of its block. Returns held
1907    /// characters to examine once more.
1908    fn resolve_end(&mut self, pending: Pending) -> Vec<Item> {
1909        match pending {
1910            Pending::Code(code) if code.closing == code.run => {
1911                self.resolve_code(code);
1912                vec![]
1913            }
1914            Pending::Code(code) => {
1915                for i in 0..code.run {
1916                    self.emit_char('`', code.at + i, code.at + i + 1);
1917                }
1918                code.buf
1919            }
1920            Pending::Link(link) => {
1921                let opener = if link.image { 2 } else { 1 };
1922                self.emit_items(&link.buf[..opener]);
1923                link.buf[opener..].to_vec()
1924            }
1925            other => {
1926                self.flush_pending(other);
1927                vec![]
1928            }
1929        }
1930    }
1931
1932    /// Draws a held context as plain text.
1933    fn flush_pending(&mut self, pending: Pending) {
1934        match pending {
1935            Pending::None => {}
1936            Pending::Escape(at) => self.emit_char('\\', at, at + 1),
1937            Pending::Bang(at) => self.emit_char('!', at, at + 1),
1938            Pending::Angle { at, .. } => self.emit_char('<', at, at + 1),
1939            Pending::Delim { ch, at, len } => self.resolve_delim(ch, at, len, ' '),
1940            Pending::Ticks { at, len } => {
1941                for i in 0..len {
1942                    self.emit_char('`', at + i, at + i + 1);
1943                }
1944            }
1945            Pending::Reference(buf) => {
1946                self.work.reexamined += bytes(&buf);
1947                self.emit_items(&buf);
1948            }
1949            Pending::Code(code) => {
1950                self.work.reexamined += code.bytes;
1951                for i in 0..code.run {
1952                    self.emit_char('`', code.at + i, code.at + i + 1);
1953                }
1954                self.emit_items(&code.buf);
1955            }
1956            Pending::Link(link) => {
1957                self.work.reexamined += link.bytes;
1958                self.emit_items(&link.buf);
1959            }
1960        }
1961    }
1962
1963    fn inline_step(&mut self, item: Item, q: &mut VecDeque<Item>) {
1964        let c = item.c;
1965        // GFM splits table cells at unescaped pipes before inline parsing.
1966        if self.inline.target == Some(Target::Cell)
1967            && c == '|'
1968            && !matches!(self.inline.pending, Pending::Escape(_))
1969        {
1970            self.cell_boundary(item);
1971            return;
1972        }
1973        match std::mem::take(&mut self.inline.pending) {
1974            Pending::None => self.plain(item, q),
1975            Pending::Escape(at) => {
1976                if c == '\n' {
1977                    let node = DisplayNode {
1978                        span: Span::new(at, item.end()),
1979                        kind: Display::HardBreak,
1980                        children: vec![],
1981                    };
1982                    self.emit_node(node, '\n');
1983                } else if c.is_ascii_punctuation() {
1984                    self.emit_char(c, at, item.end());
1985                } else {
1986                    self.emit_char('\\', at, at + 1);
1987                    q.push_front(item);
1988                }
1989            }
1990            Pending::Bang(at) => {
1991                if c == '[' {
1992                    self.inline.pending = Pending::Link(Link {
1993                        image: true,
1994                        buf: vec![
1995                            Item {
1996                                at,
1997                                c: '!',
1998                                fresh: false,
1999                            },
2000                            item,
2001                        ],
2002                        bytes: 2,
2003                        stage: Stage::Label { depth: 1 },
2004                        escaped: false,
2005                    });
2006                } else {
2007                    self.emit_char('!', at, at + 1);
2008                    q.push_front(item);
2009                }
2010            }
2011            Pending::Angle { at, block } => {
2012                if c.is_ascii_uppercase() {
2013                    self.drop_header();
2014                    self.raw_mode = Some(Raw::Capture(Box::new(Capture::new(at, block))));
2015                    self.raw_step(item, q);
2016                } else {
2017                    self.emit_char('<', at, at + 1);
2018                    q.push_front(item);
2019                }
2020            }
2021            Pending::Delim { ch, at, len } => {
2022                if c == ch {
2023                    self.inline.pending = Pending::Delim {
2024                        ch,
2025                        at,
2026                        len: len + 1,
2027                    };
2028                } else {
2029                    self.resolve_delim(ch, at, len, c);
2030                    q.push_front(item);
2031                }
2032            }
2033            Pending::Reference(buf) => self.reference(buf, item, q),
2034            Pending::Ticks { at, len } => {
2035                if c == '`' {
2036                    self.inline.pending = Pending::Ticks { at, len: len + 1 };
2037                } else {
2038                    self.inline.pending = Pending::Code(Code {
2039                        at,
2040                        run: len,
2041                        buf: vec![],
2042                        closing: 0,
2043                        bytes: len,
2044                    });
2045                    q.push_front(item);
2046                }
2047            }
2048            Pending::Code(code) => self.code_char(code, item, q),
2049            Pending::Link(link) => self.link_char(link, item, q),
2050        }
2051    }
2052
2053    fn plain(&mut self, item: Item, q: &mut VecDeque<Item>) {
2054        let (c, at) = (item.c, item.at);
2055        if c == '\n' {
2056            self.newline(at);
2057            return;
2058        }
2059        if std::mem::take(&mut self.inline.declaration) {
2060            self.drop_header();
2061            self.raw_mode = Some(Raw::Declaration(Declaration {
2062                scan: DeclarationScan::default(),
2063                cr: false,
2064                held: None,
2065            }));
2066            self.raw_step(item, q);
2067            return;
2068        }
2069        if self.inline.url {
2070            if c.is_whitespace() || c == '<' || c == '>' {
2071                self.inline.url = false;
2072            } else {
2073                self.emit_char(c, at, item.end());
2074                return;
2075            }
2076        }
2077        let block = std::mem::take(&mut self.inline.block_start);
2078        if self.inline.target == Some(Target::Cell)
2079            && !self.inline.opened
2080            && (c == ' ' || c == '\t')
2081        {
2082            return;
2083        }
2084        self.inline.leading = false;
2085        self.inline.pending = match c {
2086            '\\' => Pending::Escape(at),
2087            '`' => Pending::Ticks { at, len: 1 },
2088            '[' => Pending::Link(Link {
2089                image: false,
2090                buf: vec![item],
2091                bytes: 1,
2092                stage: Stage::Label { depth: 1 },
2093                escaped: false,
2094            }),
2095            '!' => Pending::Bang(at),
2096            '*' | '_' | '~' => Pending::Delim { ch: c, at, len: 1 },
2097            '$' => Pending::Reference(vec![item]),
2098            '<' => Pending::Angle { at, block },
2099            '{' => {
2100                // Reply prose braces are opaque literal text.
2101                self.drop_header();
2102                self.literal_char(item);
2103                self.raw_mode = Some(Raw::Braces(Braces {
2104                    depth: 1,
2105                    escaped: false,
2106                    cr: false,
2107                    held: None,
2108                }));
2109                Pending::None
2110            }
2111            _ => {
2112                self.emit_char(c, at, item.end());
2113                Pending::None
2114            }
2115        };
2116    }
2117
2118    /// A line break inside a paragraph.
2119    fn newline(&mut self, at: usize) {
2120        let hard = self.inline.spaces >= 2;
2121        if self.inline.opened {
2122            self.out.trim_run();
2123            let node = DisplayNode {
2124                span: Span::new(at, at + 1),
2125                kind: if hard {
2126                    Display::HardBreak
2127                } else {
2128                    Display::SoftBreak
2129                },
2130                children: vec![],
2131            };
2132            self.out.push(node);
2133        }
2134        self.inline.prev = '\n';
2135        self.inline.spaces = 0;
2136        self.inline.url = false;
2137        self.inline.recent.clear();
2138    }
2139
2140    fn cell_boundary(&mut self, item: Item) {
2141        if std::mem::take(&mut self.inline.leading) {
2142            return;
2143        }
2144        let opened = self.inline.opened;
2145        self.end_run();
2146        if !opened {
2147            self.out.open(Display::TableCell, item.at);
2148            self.out.close();
2149        }
2150        self.start_run(Target::Cell);
2151        self.inline.leading = false;
2152    }
2153
2154    /// Resolves a run of `*`, `_` or `~` once the character after it is
2155    /// known, following CommonMark's flanking and matching rules.
2156    fn resolve_delim(&mut self, ch: char, at: usize, len: usize, next: char) {
2157        let prev = self.inline.prev;
2158        let left = !next.is_whitespace()
2159            && (!is_punctuation(next) || prev.is_whitespace() || is_punctuation(prev));
2160        let right = !prev.is_whitespace()
2161            && (!is_punctuation(prev) || next.is_whitespace() || is_punctuation(next));
2162        let (open, close) = if ch == '_' {
2163            (
2164                left && (!right || is_punctuation(prev)),
2165                right && (!left || is_punctuation(next)),
2166            )
2167        } else {
2168            (left, right)
2169        };
2170        if ch == '~' && len != 2 {
2171            for i in 0..len {
2172                self.emit_char('~', at + i, at + i + 1);
2173            }
2174            return;
2175        }
2176        let mut count = len;
2177        let mut pos = at;
2178        while close && count > 0 {
2179            let found = self.inline.delims.iter().rposition(|d| {
2180                d.ch == ch
2181                    && (ch != '~' || d.count == count)
2182                    && !((d.close || open)
2183                        && (d.orig + len).is_multiple_of(3)
2184                        && !(d.orig.is_multiple_of(3) && len.is_multiple_of(3)))
2185            });
2186            let Some(j) = found else { break };
2187            let used = if ch == '~' || (self.inline.delims[j].count >= 2 && count >= 2) {
2188                2
2189            } else {
2190                1
2191            };
2192            let kind = match (ch, used) {
2193                ('~', _) => Display::Strikethrough,
2194                (_, 2) => Display::Strong,
2195                _ => Display::Emphasis,
2196            };
2197            self.wrap(j, used, pos, kind);
2198            count -= used;
2199            pos += used;
2200        }
2201        if count > 0 {
2202            self.ensure_frame(pos);
2203            self.out.push(DisplayNode {
2204                span: Span::new(pos, at + len),
2205                kind: Display::Text(std::iter::repeat_n(ch, count).collect()),
2206                children: vec![],
2207            });
2208            if open && self.inline.delims.len() < EMPHASIS_DEPTH {
2209                let index = self.out.top().children.len() - 1;
2210                self.inline.delims.push(Delim {
2211                    ch,
2212                    count,
2213                    orig: len,
2214                    close,
2215                    index,
2216                    start: pos,
2217                    end: at + len,
2218                });
2219            }
2220        }
2221        self.inline.prev = ch;
2222        self.inline.spaces = 0;
2223        self.inline.recent.clear();
2224    }
2225
2226    /// Wraps everything drawn after opener `j` into an emphasis node closed
2227    /// by `used` delimiters at `pos`.
2228    fn wrap(&mut self, j: usize, used: usize, pos: usize, kind: Display) {
2229        self.out.flush_run();
2230        let opener = &mut self.inline.delims[j];
2231        opener.count -= used;
2232        opener.end -= used;
2233        let (index, count, start, end, ch) = (
2234            opener.index,
2235            opener.count,
2236            opener.start,
2237            opener.end,
2238            opener.ch,
2239        );
2240        let span = Span::new(end, pos + used);
2241        let top = self.out.top();
2242        let inner = top.children.split_off(index + 1);
2243        if count == 0 {
2244            top.children.pop();
2245        } else {
2246            top.children[index] = Arc::new(DisplayNode {
2247                span: Span::new(start, end),
2248                kind: Display::Text(std::iter::repeat_n(ch, count).collect()),
2249                children: vec![],
2250            });
2251        }
2252        top.children.push(Arc::new(DisplayNode {
2253            span,
2254            kind,
2255            children: inner,
2256        }));
2257        top.span.end = top.span.end.max(span.end);
2258        self.inline.delims.truncate(j + 1);
2259        if count == 0 {
2260            self.inline.delims.pop();
2261        }
2262        self.out.dirty = true;
2263    }
2264
2265    /// A possible `$$tc(N)` reference, held at most
2266    /// [`PENDING_REFERENCE_CHARS`] characters.
2267    fn reference(&mut self, mut buf: Vec<Item>, item: Item, q: &mut VecDeque<Item>) {
2268        let c = item.c;
2269        let i = buf.len();
2270        let fits = match i {
2271            1 => c == '$',
2272            2 => c == 't',
2273            3 => c == 'c',
2274            4 => c == '(',
2275            _ => (c.is_ascii_digit() && !(i > 5 && buf[5].c == '0')) || (c == ')' && i > 5),
2276        };
2277        if !fits {
2278            // Not a reference here; one may still start at the next `$`.
2279            self.emit_char('$', buf[0].at, buf[0].end());
2280            q.push_front(item);
2281            self.rerun(q, &buf[1..]);
2282            return;
2283        }
2284        if c == ')' {
2285            let node = DisplayNode {
2286                span: Span::new(buf[0].at, item.end()),
2287                kind: Display::PendingReference,
2288                children: vec![],
2289            };
2290            self.emit_node(node, ')');
2291            return;
2292        }
2293        if buf.len() == PENDING_REFERENCE_CHARS {
2294            self.cap_flush(&buf, "", buf[0].at);
2295            q.push_front(item);
2296            return;
2297        }
2298        buf.push(item);
2299        self.inline.pending = Pending::Reference(buf);
2300    }
2301
2302    fn code_char(&mut self, mut code: Code, item: Item, q: &mut VecDeque<Item>) {
2303        if item.c == '`' {
2304            code.closing += 1;
2305        } else if code.closing == code.run {
2306            self.resolve_code(code);
2307            q.push_front(item);
2308            return;
2309        } else {
2310            code.closing = 0;
2311        }
2312        code.buf.push(item);
2313        code.bytes += item.c.len_utf8();
2314        if code.bytes > STREAM_CONTEXT_BYTES {
2315            let ticks = "`".repeat(code.run);
2316            self.cap_flush(&code.buf, &ticks, code.at);
2317            return;
2318        }
2319        self.inline.pending = Pending::Code(code);
2320    }
2321
2322    /// Draws a closed code span, normalized as CommonMark does.
2323    fn resolve_code(&mut self, code: Code) {
2324        self.work.reexamined += code.bytes;
2325        let content = &code.buf[..code.buf.len() - code.run];
2326        let mut text: String = content
2327            .iter()
2328            .map(|item| if eol(item.c) { ' ' } else { item.c })
2329            .collect();
2330        if text.len() >= 2
2331            && text.starts_with(' ')
2332            && text.ends_with(' ')
2333            && text.chars().any(|c| c != ' ')
2334        {
2335            text = text[1..text.len() - 1].to_owned();
2336        }
2337        let end = code
2338            .buf
2339            .last()
2340            .map_or(code.at + code.run, |item| item.end());
2341        let node = DisplayNode {
2342            span: Span::new(code.at, end),
2343            kind: Display::Code(sanitize_str(&text)),
2344            children: vec![],
2345        };
2346        self.emit_node(node, '`');
2347    }
2348
2349    fn link_char(&mut self, mut link: Link, item: Item, q: &mut VecDeque<Item>) {
2350        let c = item.c;
2351        link.buf.push(item);
2352        link.bytes += c.len_utf8();
2353        if link.bytes > STREAM_CONTEXT_BYTES {
2354            self.cap_flush(&link.buf, "", link.buf[0].at);
2355            return;
2356        }
2357        if std::mem::take(&mut link.escaped) {
2358            self.inline.pending = Pending::Link(link);
2359            return;
2360        }
2361        match &mut link.stage {
2362            Stage::Label { depth } => match c {
2363                '\\' => link.escaped = true,
2364                '[' => {
2365                    *depth += 1;
2366                    if *depth > LABEL_DEPTH {
2367                        self.cap_flush(&link.buf, "", link.buf[0].at);
2368                        return;
2369                    }
2370                }
2371                ']' => {
2372                    *depth -= 1;
2373                    if *depth == 0 {
2374                        link.stage = Stage::Paren;
2375                    }
2376                }
2377                _ => {}
2378            },
2379            Stage::Paren => {
2380                if c != '(' {
2381                    self.link_failed(link, q);
2382                    return;
2383                }
2384                link.stage = Stage::Destination {
2385                    nesting: 1,
2386                    quote: None,
2387                };
2388            }
2389            Stage::Destination { nesting, quote } => match (*quote, c) {
2390                (_, '\\') => link.escaped = true,
2391                (Some(open), c) if c == open => *quote = None,
2392                (Some(_), _) => {}
2393                (None, '"' | '\'') => *quote = Some(c),
2394                (None, '(') => *nesting += 1,
2395                (None, ')') => {
2396                    *nesting -= 1;
2397                    if *nesting == 0 {
2398                        self.link_complete(link, q);
2399                        return;
2400                    }
2401                }
2402                _ => {}
2403            },
2404        }
2405        self.inline.pending = Pending::Link(link);
2406    }
2407
2408    /// Not a link: the brackets are text and what they held is examined
2409    /// again.
2410    fn link_failed(&mut self, link: Link, q: &mut VecDeque<Item>) {
2411        let opener = if link.image { 2 } else { 1 };
2412        self.emit_items(&link.buf[..opener]);
2413        self.rerun(q, &link.buf[opener..]);
2414    }
2415
2416    /// Classifies a complete `[label](destination)` with the final parser.
2417    fn link_complete(&mut self, link: Link, q: &mut VecDeque<Item>) {
2418        let text: String = link.buf.iter().map(|item| item.c).collect();
2419        self.work.reexamined += text.len();
2420        let doc = parse(&text, Mode::Reply);
2421        let found = match doc.nodes.as_slice() {
2422            [Node {
2423                kind: Kind::Markdown(Markdown::Paragraph),
2424                children,
2425                ..
2426            }] => match children.as_slice() {
2427                [node @ Node {
2428                    kind: Kind::Markdown(Markdown::Link { .. }),
2429                    ..
2430                }] if node.span == Span::new(0, text.len()) => Some(node.clone()),
2431                _ => None,
2432            },
2433            _ => None,
2434        };
2435        let Some(node) = found else {
2436            self.link_failed(link, q);
2437            return;
2438        };
2439        // Offsets in the link's text map back to where each character arrived.
2440        let mut offsets = Vec::with_capacity(text.len() + 1);
2441        for item in &link.buf {
2442            offsets.extend((0..item.c.len_utf8()).map(|i| item.at + i));
2443        }
2444        offsets.push(link.buf.last().map_or(0, |item| item.end()));
2445        let display = convert(&node, &text, &|o| offsets[o.min(offsets.len() - 1)]);
2446        self.emit_node(display, ')');
2447    }
2448}
2449
2450/// Tracks a reply declaration's text to tell, at each line end, whether the
2451/// next line continues it. Mirrors the final parser's rule one character at
2452/// a time: an open bracket, tag or string, or a trailing operator, continues.
2453struct DeclarationScan {
2454    depth: i32,
2455    tags: i32,
2456    header: bool,
2457    closing: bool,
2458    header_braces: i32,
2459    quote: Option<char>,
2460    escaped: bool,
2461    operand: bool,
2462    previous: char,
2463    /// A `<` waiting for the next character to show whether it opens a tag.
2464    angle: bool,
2465}
2466
2467impl Default for DeclarationScan {
2468    fn default() -> Self {
2469        Self {
2470            depth: 0,
2471            tags: 0,
2472            header: false,
2473            closing: false,
2474            header_braces: 0,
2475            quote: None,
2476            escaped: false,
2477            operand: true,
2478            previous: '\0',
2479            angle: false,
2480        }
2481    }
2482}
2483
2484impl DeclarationScan {
2485    fn feed(&mut self, c: char) {
2486        if std::mem::take(&mut self.angle) {
2487            if c.is_ascii_uppercase() || c == '/' {
2488                self.closing = c == '/';
2489                self.tags += if self.closing { -1 } else { 1 };
2490                self.header = true;
2491                self.header_braces = 0;
2492            } else if self.tags == 0 {
2493                self.operand = true;
2494            }
2495            self.previous = '<';
2496        }
2497        if self.escaped {
2498            self.escaped = false;
2499            return;
2500        }
2501        if c == '\\' {
2502            self.escaped = true;
2503            return;
2504        }
2505        if let Some(quote) = self.quote {
2506            if c == quote {
2507                self.quote = None;
2508                self.operand = false;
2509            }
2510            return;
2511        }
2512        if (self.header || self.tags == 0) && (c == '\'' || c == '"') {
2513            self.quote = Some(c);
2514            return;
2515        }
2516        if c == '<' && !self.header && (self.tags > 0 || self.operand) {
2517            self.angle = true;
2518            return;
2519        }
2520        if self.header {
2521            match c {
2522                '{' => self.header_braces += 1,
2523                '}' => self.header_braces -= 1,
2524                '>' if self.header_braces == 0 => {
2525                    if !self.closing && self.previous == '/' {
2526                        self.tags -= 1;
2527                    }
2528                    self.header = false;
2529                    self.operand = false;
2530                }
2531                _ => {}
2532            }
2533        } else if self.tags == 0 {
2534            match c {
2535                '(' | '[' | '{' => {
2536                    self.depth += 1;
2537                    self.operand = true;
2538                }
2539                ')' | ']' | '}' => {
2540                    self.depth -= 1;
2541                    self.operand = false;
2542                }
2543                '+' | '-' | '*' | '/' | '%' | '?' | ':' | ',' | '=' | '!' | '<' | '>' | '&'
2544                | '|' => self.operand = true,
2545                c if !c.is_whitespace() => self.operand = false,
2546                _ => {}
2547            }
2548        }
2549        if !c.is_whitespace() {
2550            self.previous = c;
2551        }
2552    }
2553    fn continues(&self) -> bool {
2554        self.depth > 0 || self.tags > 0 || self.header || self.operand || self.angle
2555    }
2556}
2557
2558/// Scans a tag opening as the final parser bounds a failed one: through the
2559/// first `>` outside strings and braces.
2560#[derive(Default)]
2561struct TagScan {
2562    quote: Option<char>,
2563    braces: usize,
2564    escaped: bool,
2565    prev: char,
2566}
2567
2568impl TagScan {
2569    /// Returns whether the opening self-closes once its `>` arrives.
2570    fn feed(&mut self, c: char) -> Option<bool> {
2571        let prev = std::mem::replace(&mut self.prev, c);
2572        if self.escaped {
2573            self.escaped = false;
2574        } else if c == '\\' {
2575            self.escaped = true;
2576        } else if let Some(quote) = self.quote {
2577            if c == quote {
2578                self.quote = None;
2579            }
2580        } else if c == '\'' || c == '"' {
2581            self.quote = Some(c);
2582        } else if c == '{' {
2583            self.braces += 1;
2584        } else if c == '}' {
2585            self.braces = self.braces.saturating_sub(1);
2586        } else if c == '>' && self.braces == 0 {
2587            return Some(prev == '/');
2588        }
2589        None
2590    }
2591}
2592
2593#[derive(Clone, Copy, PartialEq, Eq)]
2594enum CapturePhase {
2595    /// The outer `<Name …>`.
2596    Opening,
2597    /// After the outer opening: the rest of its line decides between a block
2598    /// and an inline tag.
2599    Suffix {
2600        self_closing: bool,
2601    },
2602    Body,
2603    /// A block tag's close was seen; its line must end before the tag is
2604    /// complete.
2605    CloseLine,
2606}
2607
2608/// Where the body scan stands inside one tag-like construct.
2609enum Inner {
2610    Plain,
2611    Angle,
2612    Open(TagScan),
2613    Close { named: bool, spaced: bool },
2614}
2615
2616/// A tag held whole until complete, then classified by the final parser.
2617/// The scan only decides where the tag ends; the final parser decides what
2618/// it is.
2619struct Capture {
2620    base: usize,
2621    text: String,
2622    /// Leading bytes of `text` that already passed line classification.
2623    inline_bytes: usize,
2624    block_candidate: bool,
2625    phase: CapturePhase,
2626    opener: TagScan,
2627    opener_end: usize,
2628    block: bool,
2629    depth: usize,
2630    braces: usize,
2631    escaped: bool,
2632    inner: Inner,
2633    line_start: usize,
2634    line_blank: bool,
2635    first_line: bool,
2636    cr: bool,
2637}
2638
2639impl Capture {
2640    fn new(base: usize, block_candidate: bool) -> Self {
2641        Self {
2642            base,
2643            text: "<".to_owned(),
2644            inline_bytes: 1,
2645            block_candidate,
2646            phase: CapturePhase::Opening,
2647            opener: TagScan {
2648                prev: '<',
2649                ..TagScan::default()
2650            },
2651            opener_end: 0,
2652            block: false,
2653            depth: 0,
2654            braces: 0,
2655            escaped: false,
2656            inner: Inner::Plain,
2657            line_start: 0,
2658            line_blank: false,
2659            first_line: true,
2660            cr: false,
2661        }
2662    }
2663
2664    /// Feeds one body character; returns true at the outer tag's close.
2665    fn body(&mut self, c: char) -> bool {
2666        if std::mem::take(&mut self.escaped) {
2667            return false;
2668        }
2669        match &mut self.inner {
2670            Inner::Plain => match c {
2671                '\\' => self.escaped = true,
2672                '<' if self.braces == 0 => self.inner = Inner::Angle,
2673                '{' => self.braces += 1,
2674                '}' => self.braces = self.braces.saturating_sub(1),
2675                _ => {}
2676            },
2677            Inner::Angle => {
2678                if c.is_ascii_uppercase() {
2679                    self.inner = Inner::Open(TagScan::default());
2680                } else if c == '/' {
2681                    self.inner = Inner::Close {
2682                        named: false,
2683                        spaced: false,
2684                    };
2685                } else {
2686                    self.inner = Inner::Plain;
2687                    return self.body(c);
2688                }
2689            }
2690            Inner::Open(scan) => {
2691                if let Some(self_closing) = scan.feed(c) {
2692                    self.depth += usize::from(!self_closing);
2693                    self.inner = Inner::Plain;
2694                }
2695            }
2696            Inner::Close { named, spaced } => {
2697                if c.is_ascii_alphanumeric() && !*spaced && (*named || c.is_ascii_uppercase()) {
2698                    *named = true;
2699                } else if c.is_whitespace() && *named {
2700                    *spaced = true;
2701                } else if c == '>' && *named {
2702                    self.inner = Inner::Plain;
2703                    if self.depth == 0 {
2704                        return true;
2705                    }
2706                    self.depth -= 1;
2707                } else {
2708                    self.inner = Inner::Plain;
2709                }
2710            }
2711        }
2712        false
2713    }
2714}
2715
2716impl Machine {
2717    fn raw_step(&mut self, item: Item, q: &mut VecDeque<Item>) {
2718        match self.raw_mode.take() {
2719            Some(Raw::Braces(braces)) => self.braces_step(braces, item, q),
2720            Some(Raw::Declaration(declaration)) => self.declaration_step(declaration, item, q),
2721            Some(Raw::Capture(capture)) => self.capture_step(capture, item, q),
2722            None => {}
2723        }
2724    }
2725
2726    /// The paragraph's current line ended inside verbatim text; the next
2727    /// character starts a line.
2728    fn resume_line_start(&mut self) {
2729        self.line = Line::start();
2730        self.eol = None;
2731    }
2732
2733    fn braces_step(&mut self, mut b: Braces, item: Item, q: &mut VecDeque<Item>) {
2734        let c = item.c;
2735        if let Some(held) = &mut b.held {
2736            if c == '\n' && b.cr {
2737                b.cr = false;
2738                self.literal_char(item);
2739                self.raw_mode = Some(Raw::Braces(b));
2740                return;
2741            }
2742            if c == ' ' || c == '\t' {
2743                held.push(item);
2744                self.raw_mode = Some(Raw::Braces(b));
2745                return;
2746            }
2747            let mut held = std::mem::take(held);
2748            b.held = None;
2749            if eol(c) {
2750                // A blank line ends unclosed braces before it.
2751                held.push(item);
2752                self.resume_line_start();
2753                self.rerun(q, &held);
2754                return;
2755            }
2756            for item in held {
2757                self.literal_char(item);
2758            }
2759        }
2760        self.literal_char(item);
2761        b.cr = c == '\r';
2762        if eol(c) {
2763            b.escaped = false;
2764            b.held = Some(vec![]);
2765        } else if b.escaped {
2766            b.escaped = false;
2767        } else if c == '\\' {
2768            b.escaped = true;
2769        } else if c == '{' {
2770            b.depth += 1;
2771        } else if c == '}' {
2772            b.depth -= 1;
2773            if b.depth == 0 {
2774                return;
2775            }
2776        }
2777        self.raw_mode = Some(Raw::Braces(b));
2778    }
2779
2780    fn declaration_step(&mut self, mut d: Declaration, item: Item, q: &mut VecDeque<Item>) {
2781        let c = item.c;
2782        if let Some((held, prefix)) = &mut d.held {
2783            if c == '\n' && d.cr && held.len() == 1 {
2784                d.cr = false;
2785                held.push(item);
2786                self.raw_mode = Some(Raw::Declaration(d));
2787                return;
2788            }
2789            if prefix.is_empty() && (c == ' ' || c == '\t') {
2790                held.push(item);
2791                self.raw_mode = Some(Raw::Declaration(d));
2792                return;
2793            }
2794            let continues = if prefix.is_empty() && eol(c) {
2795                false
2796            } else if prefix.is_empty() && d.scan.continues() {
2797                true
2798            } else {
2799                prefix.push(c);
2800                match prefix.as_str() {
2801                    "&" | "|" => {
2802                        held.push(item);
2803                        self.raw_mode = Some(Raw::Declaration(d));
2804                        return;
2805                    }
2806                    "&&" | "||" => true,
2807                    p => p.starts_with(['?', ':', '.']),
2808                }
2809            };
2810            let mut held = std::mem::take(held);
2811            d.held = None;
2812            if !continues {
2813                // The declaration ended at its line break; the held text is
2814                // ordinary input again.
2815                held.push(item);
2816                self.rerun(q, &held);
2817                return;
2818            }
2819            for item in held {
2820                self.literal_char(item);
2821                d.scan.feed(item.c);
2822            }
2823        }
2824        if eol(c) {
2825            d.held = Some((vec![item], String::new()));
2826            d.cr = c == '\r';
2827        } else {
2828            self.literal_char(item);
2829            d.scan.feed(c);
2830        }
2831        self.raw_mode = Some(Raw::Declaration(d));
2832    }
2833
2834    fn capture_step(&mut self, mut cap: Box<Capture>, item: Item, q: &mut VecDeque<Item>) {
2835        let c = item.c;
2836        let crlf = c == '\n' && cap.cr;
2837        let line_end = eol(c) && !crlf;
2838        let blank = |c: char| c == ' ' || c == '\t';
2839        match cap.phase {
2840            CapturePhase::Suffix { self_closing } if line_end => {
2841                if self_closing {
2842                    let (end, block) = (cap.text.len(), cap.block_candidate);
2843                    return self.complete_capture(&cap, end, block, item, q);
2844                }
2845                cap.block = cap.block_candidate;
2846                cap.phase = CapturePhase::Body;
2847            }
2848            CapturePhase::Suffix { self_closing } if !blank(c) => {
2849                if self_closing {
2850                    let end = cap.opener_end;
2851                    return self.complete_capture(&cap, end, false, item, q);
2852                }
2853                cap.block = false;
2854                cap.phase = CapturePhase::Body;
2855            }
2856            CapturePhase::CloseLine if line_end => {
2857                let (end, block) = (cap.text.len(), cap.block_candidate);
2858                return self.complete_capture(&cap, end, block, item, q);
2859            }
2860            _ => {}
2861        }
2862        if line_end && !cap.first_line && cap.line_blank {
2863            // A blank line ends a failed opening and an inline tag's body,
2864            // but not a block tag's body.
2865            let ends = match cap.phase {
2866                CapturePhase::Opening => true,
2867                CapturePhase::Body => !cap.block,
2868                _ => false,
2869            };
2870            if ends {
2871                let (end, block) = (cap.line_start, cap.block_candidate);
2872                return self.complete_capture(&cap, end, block, item, q);
2873            }
2874            cap.braces = 0;
2875            cap.inner = Inner::Plain;
2876        }
2877        cap.text.push(c);
2878        if !item.fresh {
2879            cap.inline_bytes = cap.text.len();
2880        }
2881        if line_end || crlf {
2882            cap.line_start = cap.text.len();
2883            cap.line_blank = true;
2884            cap.first_line = false;
2885        } else if !blank(c) {
2886            cap.line_blank = false;
2887        }
2888        cap.cr = c == '\r';
2889        match cap.phase {
2890            CapturePhase::Opening => {
2891                if let Some(self_closing) = cap.opener.feed(c) {
2892                    cap.opener_end = cap.text.len();
2893                    // Only a tag at a block position waits for the rest of
2894                    // its line to decide between block and inline.
2895                    if cap.block_candidate {
2896                        cap.phase = CapturePhase::Suffix { self_closing };
2897                    } else if self_closing {
2898                        let end = cap.text.len();
2899                        return self.complete_capture_now(&cap, end, false, q);
2900                    } else {
2901                        cap.phase = CapturePhase::Body;
2902                    }
2903                } else if cap.text.len() > TAG_OPENING_BYTES {
2904                    return self.capture_cap(&cap);
2905                }
2906            }
2907            CapturePhase::Body => {
2908                if cap.body(c) {
2909                    if cap.block {
2910                        cap.phase = CapturePhase::CloseLine;
2911                    } else {
2912                        let (end, block) = (cap.text.len(), cap.block_candidate);
2913                        return self.complete_capture_now(&cap, end, block, q);
2914                    }
2915                }
2916            }
2917            CapturePhase::Suffix { .. } | CapturePhase::CloseLine => {}
2918        }
2919        if cap.text.len() > STREAM_CONTEXT_BYTES {
2920            return self.capture_cap(&cap);
2921        }
2922        self.raw_mode = Some(Raw::Capture(cap));
2923    }
2924
2925    /// A held tag reached its cap: draw it as a provisional literal.
2926    fn capture_cap(&mut self, cap: &Capture) {
2927        self.work.reexamined += cap.text.len();
2928        let node = DisplayNode {
2929            span: Span::new(cap.base, cap.base + cap.text.len()),
2930            kind: Display::Literal(sanitize_str(&cap.text)),
2931            children: vec![],
2932        };
2933        let prev = cap.text.chars().last().unwrap_or('<');
2934        self.emit_node(node, prev);
2935        if eol(prev) {
2936            self.resume_line_start();
2937        }
2938    }
2939
2940    fn complete_capture(
2941        &mut self,
2942        cap: &Capture,
2943        end: usize,
2944        block_candidate: bool,
2945        next: Item,
2946        q: &mut VecDeque<Item>,
2947    ) {
2948        q.push_front(next);
2949        self.complete_capture_now(cap, end, block_candidate, q);
2950    }
2951
2952    /// Classifies the held tag `text[..end]` with the final parser, draws the
2953    /// result, and queues whatever the tag did not consume.
2954    fn complete_capture_now(
2955        &mut self,
2956        cap: &Capture,
2957        end: usize,
2958        block_candidate: bool,
2959        q: &mut VecDeque<Item>,
2960    ) {
2961        let text = &cap.text[..end];
2962        self.work.reexamined += end;
2963        let mut engine = Engine::new(text, end, Mode::Reply);
2964        let (node, stop) = match engine.tag(0, end, 1, block_candidate) {
2965            Ok((node, stop, _)) if stop > 0 && stop <= end => (node, stop),
2966            _ => (
2967                Node {
2968                    span: Span::new(0, end),
2969                    kind: Kind::Literal(text.to_owned()),
2970                    children: vec![],
2971                },
2972                end,
2973            ),
2974        };
2975        let display = convert(&node, text, &|o| cap.base + o);
2976        let prev = text[..stop].chars().last().unwrap_or('>');
2977        if matches!(node.kind, Kind::Tag { block: true, .. }) {
2978            // A block tag ends the paragraph it interrupts.
2979            if matches!(self.leaf, Leaf::Paragraph { .. }) {
2980                self.close_leaf();
2981            }
2982            self.out.push(display);
2983        } else {
2984            self.emit_node(display, prev);
2985        }
2986        if eol(prev) {
2987            self.resume_line_start();
2988        }
2989        let rest: Vec<Item> = cap.text[stop..]
2990            .char_indices()
2991            .map(|(i, c)| Item {
2992                at: cap.base + stop + i,
2993                c,
2994                fresh: stop + i >= cap.inline_bytes,
2995            })
2996            .collect();
2997        self.rerun(q, &rest);
2998    }
2999
3000    /// Ends verbatim text left open when its block ends.
3001    fn finish_raw(&mut self) {
3002        match self.raw_mode.take() {
3003            Some(Raw::Capture(cap)) => self.capture_cap(&cap),
3004            Some(Raw::Declaration(Declaration {
3005                held: Some((held, _)),
3006                ..
3007            })) => {
3008                for item in held {
3009                    self.literal_char(item);
3010                }
3011            }
3012            _ => {}
3013        }
3014    }
3015}