This commit is contained in:
Noah Hellman 2022-12-17 18:03:06 +01:00
parent cd54416902
commit 5ac05d1919
4 changed files with 85 additions and 29 deletions

View file

@ -52,6 +52,7 @@ struct Writer<I, W> {
events: I, events: I,
out: W, out: W,
raw: Raw, raw: Raw,
text_only: bool,
} }
impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> { impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
@ -60,6 +61,7 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
events, events,
out, out,
raw: Raw::None, raw: Raw::None,
text_only: false,
} }
} }
@ -70,6 +72,9 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
if c.is_block() { if c.is_block() {
self.out.write_char('\n')?; self.out.write_char('\n')?;
} }
if self.text_only && !matches!(c, Container::Image(..)) {
continue;
}
match c { match c {
Container::Blockquote => self.out.write_str("<blockquote>")?, Container::Blockquote => self.out.write_str("<blockquote>")?,
Container::List(..) => todo!(), Container::List(..) => todo!(),
@ -98,8 +103,11 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
} }
} }
Container::Span => self.out.write_str("<span>")?, Container::Span => self.out.write_str("<span>")?,
Container::Link(..) => todo!(), Container::Link(dst, ..) => write!(self.out, r#"<a href="{}">"#, dst)?,
Container::Image(..) => todo!(), Container::Image(..) => {
self.text_only = true;
self.out.write_str("<img")?;
}
Container::Verbatim => self.out.write_str("<code>")?, Container::Verbatim => self.out.write_str("<code>")?,
Container::Math { display } => self.out.write_str(if display { Container::Math { display } => self.out.write_str(if display {
r#"<span class="math display">\["# r#"<span class="math display">\["#
@ -128,6 +136,9 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
if c.is_block_container() && !matches!(c, Container::Footnote { .. }) { if c.is_block_container() && !matches!(c, Container::Footnote { .. }) {
self.out.write_char('\n')?; self.out.write_char('\n')?;
} }
if self.text_only && !matches!(c, Container::Image(..)) {
continue;
}
match c { match c {
Container::Blockquote => self.out.write_str("</blockquote>")?, Container::Blockquote => self.out.write_str("</blockquote>")?,
Container::List(..) => todo!(), Container::List(..) => todo!(),
@ -143,13 +154,21 @@ impl<'s, I: Iterator<Item = Event<'s>>, W: std::fmt::Write> Writer<I, W> {
Container::TableCell => self.out.write_str("</td>")?, Container::TableCell => self.out.write_str("</td>")?,
Container::DescriptionTerm => self.out.write_str("</dt>")?, Container::DescriptionTerm => self.out.write_str("</dt>")?,
Container::CodeBlock { .. } => self.out.write_str("</code></pre>")?, Container::CodeBlock { .. } => self.out.write_str("</code></pre>")?,
Container::Span => self.out.write_str("</span>")?, Container::Span | Container::Math { .. } => {
Container::Link(..) => todo!(), self.out.write_str("</span>")?;
Container::Image(..) => todo!(), }
Container::Link(..) => self.out.write_str("</a>")?,
Container::Image(src, ..) => {
self.text_only = false;
if src.is_empty() {
self.out.write_str(r#"">"#)?;
} else {
write!(self.out, r#"" src="{}">"#, src)?;
}
}
Container::Verbatim => self.out.write_str("</code>")?, Container::Verbatim => self.out.write_str("</code>")?,
Container::Math { .. } => self.out.write_str("</span>")?,
Container::RawBlock { .. } | Container::RawInline { .. } => { Container::RawBlock { .. } | Container::RawInline { .. } => {
self.raw = Raw::None self.raw = Raw::None;
} }
Container::Subscript => self.out.write_str("</sub>")?, Container::Subscript => self.out.write_str("</sub>")?,
Container::Superscript => self.out.write_str("</sup>")?, Container::Superscript => self.out.write_str("</sup>")?,

View file

@ -40,9 +40,12 @@ pub enum Container {
DisplayMath, DisplayMath,
/// Span is the reference link tag. /// Span is the reference link tag.
ReferenceLink, ReferenceLink,
/// Span is the reference link tag.
/// Delimiter spans are the URL. ReferenceImage,
/// Span is the URL.
InlineLink, InlineLink,
/// Span is the URL.
InlineImage,
AutoLink, AutoLink,
} }
@ -70,7 +73,7 @@ pub struct Parser<I> {
/// Stack with kind and index of _potential_ openers for typesetting containers. /// Stack with kind and index of _potential_ openers for typesetting containers.
typesets: Vec<(Container, usize)>, typesets: Vec<(Container, usize)>,
/// Stack with index of _potential_ span/link openers. /// Stack with index of _potential_ span/link openers.
spans: Vec<usize>, spans: Vec<(usize, bool)>,
//attributes: Vec<(Span, usize)>, //attributes: Vec<(Span, usize)>,
/// Buffer queue for next events. Events are buffered until no modifications due to future /// Buffer queue for next events. Events are buffered until no modifications due to future
/// characters are needed. /// characters are needed.
@ -211,13 +214,14 @@ impl<I: Iterator<Item = char> + Clone> Parser<I> {
fn parse_span(&mut self, first: &lex::Token) -> Option<Event> { fn parse_span(&mut self, first: &lex::Token) -> Option<Event> {
match first.kind { match first.kind {
lex::Kind::Open(Delimiter::Bracket) => Some(true), lex::Kind::Sym(Symbol::ExclaimBracket) => Some((true, true)),
lex::Kind::Close(Delimiter::Bracket) => Some(false), lex::Kind::Open(Delimiter::Bracket) => Some((true, false)),
lex::Kind::Close(Delimiter::Bracket) => Some((false, false)),
_ => None, _ => None,
} }
.and_then(|open| { .and_then(|(open, img)| {
if open { if open {
self.spans.push(self.events.len()); self.spans.push((self.events.len(), img));
// use str for now, replace if closed later // use str for now, replace if closed later
Some(Event { Some(Event {
kind: EventKind::Str, kind: EventKind::Str,
@ -225,10 +229,13 @@ impl<I: Iterator<Item = char> + Clone> Parser<I> {
}) })
} else if !self.spans.is_empty() { } else if !self.spans.is_empty() {
let mut ahead = self.lexer.inner().clone(); let mut ahead = self.lexer.inner().clone();
let img = self.spans.last().unwrap().1;
match ahead.next() { match ahead.next() {
Some(opener @ ('[' | '(')) => { Some(opener @ ('[' | '(')) => {
let (closer, kind) = match opener { let (closer, kind) = match opener {
'[' if img => (']', ReferenceImage),
'[' => (']', ReferenceLink), '[' => (']', ReferenceLink),
'(' if img => (')', InlineImage),
'(' => (')', InlineLink), '(' => (')', InlineLink),
_ => unreachable!(), _ => unreachable!(),
}; };
@ -251,7 +258,7 @@ impl<I: Iterator<Item = char> + Clone> Parser<I> {
} }
.map(|(kind, span)| { .map(|(kind, span)| {
self.lexer = lex::Lexer::new(ahead); self.lexer = lex::Lexer::new(ahead);
let opener_event = self.spans.pop().unwrap(); let (opener_event, _) = self.spans.pop().unwrap();
self.events[opener_event].kind = EventKind::Enter(kind); self.events[opener_event].kind = EventKind::Enter(kind);
self.events[opener_event].span = span; self.events[opener_event].span = span;
self.span = span.translate(1); self.span = span.translate(1);
@ -527,6 +534,12 @@ mod test {
(Str, "text"), (Str, "text"),
(Exit(ReferenceLink), "tag"), (Exit(ReferenceLink), "tag"),
); );
test_parse!(
"![text][tag]",
(Enter(ReferenceImage), "tag"),
(Str, "text"),
(Exit(ReferenceImage), "tag"),
);
test_parse!( test_parse!(
"before [text][tag] after", "before [text][tag] after",
(Str, "before "), (Str, "before "),

View file

@ -46,7 +46,7 @@ pub enum Symbol {
Asterisk, Asterisk,
Caret, Caret,
Equal, Equal,
Exclaim, ExclaimBracket,
Gt, Gt,
Lt, Lt,
Percentage, Percentage,
@ -217,7 +217,10 @@ impl<I: Iterator<Item = char> + Clone> Lexer<I> {
} }
} }
'!' => Sym(Exclaim), '!' if self.peek_char() == '[' => {
self.eat_char();
Sym(ExclaimBracket)
}
'%' => Sym(Percentage), '%' => Sym(Percentage),
'<' => Sym(Lt), '<' => Sym(Lt),
'>' => Sym(Gt), '>' => Sym(Gt),
@ -349,12 +352,12 @@ mod test {
#[test] #[test]
fn sym() { fn sym() {
test_lex!( test_lex!(
r#"'*^=!><%|+"~_"#, r#"'*^=![><%|+"~_"#,
Sym(Quote1).l(1), Sym(Quote1).l(1),
Sym(Asterisk).l(1), Sym(Asterisk).l(1),
Sym(Caret).l(1), Sym(Caret).l(1),
Sym(Equal).l(1), Sym(Equal).l(1),
Sym(Exclaim).l(1), Sym(ExclaimBracket).l(2),
Sym(Gt).l(1), Sym(Gt).l(1),
Sym(Lt).l(1), Sym(Lt).l(1),
Sym(Percentage).l(1), Sym(Percentage).l(1),

View file

@ -60,8 +60,8 @@ pub enum Container<'s> {
Span, Span,
/// An inline link with a destination URL. /// An inline link with a destination URL.
Link(CowStr<'s>, LinkType), Link(CowStr<'s>, LinkType),
/// An inline image. /// An inline image with a source URL.
Image(CowStr<'s>), Image(CowStr<'s>, SpanLinkType),
/// An inline verbatim string. /// An inline verbatim string.
Verbatim, Verbatim,
/// An inline or display math element. /// An inline or display math element.
@ -163,9 +163,14 @@ impl<'s> Container<'s> {
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum LinkType { pub enum SpanLinkType {
Inline, Inline,
Reference, Reference,
}
#[derive(Debug, PartialEq, Eq)]
pub enum LinkType {
Span(SpanLinkType),
AutoLink, AutoLink,
Email, Email,
} }
@ -321,7 +326,6 @@ impl<'s> Parser<'s> {
impl<'s> Parser<'s> { impl<'s> Parser<'s> {
fn inline(&self, inline: inline::Event) -> Event<'s> { fn inline(&self, inline: inline::Event) -> Event<'s> {
//let content = inline.span.of(self.src);
match inline.kind { match inline.kind {
inline::EventKind::Enter(c) | inline::EventKind::Exit(c) => { inline::EventKind::Enter(c) | inline::EventKind::Exit(c) => {
let t = match c { let t = match c {
@ -343,9 +347,13 @@ impl<'s> Parser<'s> {
inline::Container::DoubleQuoted => Container::DoubleQuoted, inline::Container::DoubleQuoted => Container::DoubleQuoted,
inline::Container::InlineLink => Container::Link( inline::Container::InlineLink => Container::Link(
self.inline_str(inline.span).replace('\n', "").into(), self.inline_str(inline.span).replace('\n', "").into(),
LinkType::Inline, LinkType::Span(SpanLinkType::Inline),
), ),
_ => todo!(), inline::Container::InlineImage => Container::Image(
self.inline_str(inline.span).replace('\n', "").into(),
SpanLinkType::Inline,
),
_ => todo!("{:?}", c),
}; };
if matches!(inline.kind, inline::EventKind::Enter(_)) { if matches!(inline.kind, inline::EventKind::Enter(_)) {
Event::Start(t, Attributes::none()) Event::Start(t, Attributes::none())
@ -470,6 +478,7 @@ mod test {
use super::CowStr; use super::CowStr;
use super::Event::*; use super::Event::*;
use super::LinkType; use super::LinkType;
use super::SpanLinkType;
macro_rules! test_parse { macro_rules! test_parse {
($src:expr $(,$($token:expr),* $(,)?)?) => { ($src:expr $(,$($token:expr),* $(,)?)?) => {
@ -616,11 +625,17 @@ mod test {
"[text](url)", "[text](url)",
Start(Paragraph, Attributes::none()), Start(Paragraph, Attributes::none()),
Start( Start(
Link(CowStr::Borrowed("url"), LinkType::Inline), Link(
CowStr::Borrowed("url"),
LinkType::Span(SpanLinkType::Inline),
),
Attributes::none() Attributes::none()
), ),
Str(CowStr::Borrowed("text")), Str(CowStr::Borrowed("text")),
End(Link(CowStr::Borrowed("url"), LinkType::Inline)), End(Link(
CowStr::Borrowed("url"),
LinkType::Span(SpanLinkType::Inline)
)),
End(Paragraph), End(Paragraph),
); );
test_parse!( test_parse!(
@ -631,11 +646,17 @@ mod test {
Start(Blockquote, Attributes::none()), Start(Blockquote, Attributes::none()),
Start(Paragraph, Attributes::none()), Start(Paragraph, Attributes::none()),
Start( Start(
Link(CowStr::Owned("urlurl".to_string()), LinkType::Inline), Link(
CowStr::Owned("urlurl".to_string()),
LinkType::Span(SpanLinkType::Inline)
),
Attributes::none() Attributes::none()
), ),
Str(CowStr::Borrowed("text")), Str(CowStr::Borrowed("text")),
End(Link(CowStr::Borrowed("urlurl"), LinkType::Inline)), End(Link(
CowStr::Borrowed("urlurl"),
LinkType::Span(SpanLinkType::Inline)
)),
End(Paragraph), End(Paragraph),
End(Blockquote), End(Blockquote),
); );