lib: emit footnotes as they are encountered

Previously, footnotes and their children events were skipped (stored in
block tree) and inline parsed at the end. Now, they are emitted by the
parser immediately and the responsibility to aggregate them has been
moved to the renderer.

resolves #31
This commit is contained in:
Noah Hellman 2023-04-08 11:52:58 +02:00
parent c4ecd0c677
commit 99f4691e52
3 changed files with 144 additions and 290 deletions

View file

@ -209,7 +209,7 @@ pub enum Event<'s> {
/// A string object, text only.
Str(CowStr<'s>),
/// A footnote reference.
FootnoteReference(&'s str, usize),
FootnoteReference(&'s str),
/// A symbol, by default rendered literally but may be treated specially.
Symbol(CowStr<'s>),
/// Left single quotation mark.
@ -262,7 +262,7 @@ pub enum Container<'s> {
/// Details describing a term within a description list.
DescriptionDetails,
/// A footnote definition.
Footnote { tag: &'s str, number: usize },
Footnote { label: &'s str },
/// A table element.
Table,
/// A row element of a table.
@ -569,15 +569,6 @@ pub struct Parser<'s> {
/// Currently within a verbatim code block.
verbatim: bool,
/// Footnote references in the order they were encountered, without duplicates.
footnote_references: Vec<&'s str>,
/// Cache of footnotes to emit at the end.
footnotes: Map<&'s str, block::Tree>,
/// Next or current footnote being parsed and emitted.
footnote_index: usize,
/// Currently within a footnote.
footnote_active: bool,
/// Inline parser.
inline_parser: inline::Parser<'s>,
}
@ -755,10 +746,6 @@ impl<'s> Parser<'s> {
block_attributes: Attributes::new(),
table_head_row: false,
verbatim: false,
footnote_references: Vec::new(),
footnotes: Map::new(),
footnote_index: 0,
footnote_active: false,
inline_parser,
}
}
@ -847,19 +834,7 @@ impl<'s> Parser<'s> {
}
inline::EventKind::Atom(a) => match a {
inline::Atom::FootnoteReference => {
let tag = inline.span.of(self.src);
let number = self
.footnote_references
.iter()
.position(|t| *t == tag)
.map_or_else(
|| {
self.footnote_references.push(tag);
self.footnote_references.len()
},
|i| i + 1,
);
Event::FootnoteReference(inline.span.of(self.src), number)
Event::FootnoteReference(inline.span.of(self.src))
}
inline::Atom::Symbol => Event::Symbol(inline.span.of(self.src).into()),
inline::Atom::Quote { ty, left } => match (ty, left) {
@ -941,12 +916,7 @@ impl<'s> Parser<'s> {
block::Container::Div { .. } => Container::Div {
class: (!ev.span.is_empty()).then(|| content),
},
block::Container::Footnote => {
debug_assert!(enter);
self.footnotes.insert(content, self.tree.take_branch());
self.block_attributes = Attributes::new();
continue;
}
block::Container::Footnote => Container::Footnote { label: content },
block::Container::List(block::ListKind { ty, tight }) => {
if matches!(ty, block::ListType::Description) {
Container::DescriptionList
@ -1013,43 +983,13 @@ impl<'s> Parser<'s> {
}
None
}
fn footnote(&mut self) -> Option<Event<'s>> {
if self.footnote_active {
let tag = self.footnote_references.get(self.footnote_index).unwrap();
self.footnote_index += 1;
self.footnote_active = false;
Some(Event::End(Container::Footnote {
tag,
number: self.footnote_index,
}))
} else if let Some(tag) = self.footnote_references.get(self.footnote_index) {
self.tree = self
.footnotes
.remove(tag)
.unwrap_or_else(block::Tree::empty);
self.footnote_active = true;
Some(Event::Start(
Container::Footnote {
tag,
number: self.footnote_index + 1,
},
Attributes::new(),
))
} else {
None
}
}
}
impl<'s> Iterator for Parser<'s> {
type Item = Event<'s>;
fn next(&mut self) -> Option<Self::Item> {
self.inline()
.or_else(|| self.block())
.or_else(|| self.footnote())
self.inline().or_else(|| self.block())
}
}
@ -1563,43 +1503,10 @@ mod test {
test_parse!(
"[^a][^b][^c]",
Start(Paragraph, Attributes::new()),
FootnoteReference("a", 1),
FootnoteReference("b", 2),
FootnoteReference("c", 3),
FootnoteReference("a"),
FootnoteReference("b"),
FootnoteReference("c"),
End(Paragraph),
Start(
Footnote {
tag: "a",
number: 1
},
Attributes::new()
),
End(Footnote {
tag: "a",
number: 1
}),
Start(
Footnote {
tag: "b",
number: 2
},
Attributes::new()
),
End(Footnote {
tag: "b",
number: 2
}),
Start(
Footnote {
tag: "c",
number: 3
},
Attributes::new()
),
End(Footnote {
tag: "c",
number: 3
}),
);
}
@ -1608,23 +1515,14 @@ mod test {
test_parse!(
"[^a]\n\n[^a]: a\n",
Start(Paragraph, Attributes::new()),
FootnoteReference("a", 1),
FootnoteReference("a"),
End(Paragraph),
Blankline,
Start(
Footnote {
tag: "a",
number: 1
},
Attributes::new()
),
Start(Footnote { label: "a" }, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("a".into()),
End(Paragraph),
End(Footnote {
tag: "a",
number: 1
}),
End(Footnote { label: "a" }),
);
}
@ -1639,16 +1537,10 @@ mod test {
" def", //
),
Start(Paragraph, Attributes::new()),
FootnoteReference("a", 1),
FootnoteReference("a"),
End(Paragraph),
Blankline,
Start(
Footnote {
tag: "a",
number: 1
},
Attributes::new()
),
Start(Footnote { label: "a" }, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("abc".into()),
End(Paragraph),
@ -1656,10 +1548,7 @@ mod test {
Start(Paragraph, Attributes::new()),
Str("def".into()),
End(Paragraph),
End(Footnote {
tag: "a",
number: 1
}),
End(Footnote { label: "a" }),
);
}
@ -1673,26 +1562,17 @@ mod test {
"para\n", //
),
Start(Paragraph, Attributes::new()),
FootnoteReference("a", 1),
FootnoteReference("a"),
End(Paragraph),
Blankline,
Start(Paragraph, Attributes::new()),
Str("para".into()),
End(Paragraph),
Start(
Footnote {
tag: "a",
number: 1
},
Attributes::new()
),
Start(Footnote { label: "a" }, Attributes::new()),
Start(Paragraph, Attributes::new()),
Str("note".into()),
End(Paragraph),
End(Footnote {
tag: "a",
number: 1
}),
End(Footnote { label: "a" }),
Start(Paragraph, Attributes::new()),
Str("para".into()),
End(Paragraph),
);
}