diff --git a/src/block.rs b/src/block.rs index b35cc72..c18a12a 100644 --- a/src/block.rs +++ b/src/block.rs @@ -306,8 +306,8 @@ mod test { test_parse!( "para\n", (Enter(Leaf(Paragraph)), ""), - (Element(Inline), "para\n"), - (Exit, ""), + (Element(Inline), "para"), + (Exit(Leaf(Paragraph)), ""), ); } @@ -317,8 +317,8 @@ mod test { "para0\npara1\n", (Enter(Leaf(Paragraph)), ""), (Element(Inline), "para0\n"), - (Element(Inline), "para1\n"), - (Exit, ""), + (Element(Inline), "para1"), + (Exit(Leaf(Paragraph)), ""), ); } @@ -333,14 +333,14 @@ mod test { "15\n", // ), (Enter(Leaf(Heading { level: 1 })), "#"), - (Element(Inline), " 2\n"), - (Exit, "#"), + (Element(Inline), "2"), + (Exit(Leaf(Heading { level: 1 })), "#"), (Element(Blankline), "\n"), (Enter(Leaf(Heading { level: 1 })), "#"), (Element(Inline), " 8\n"), (Element(Inline), " 12\n"), - (Element(Inline), "15\n"), - (Exit, "#"), + (Element(Inline), "15"), + (Exit(Leaf(Heading { level: 1 })), "#"), ); } @@ -356,17 +356,17 @@ mod test { ), (Enter(Container(Blockquote)), ">"), (Enter(Leaf(Paragraph)), ""), - (Element(Inline), " a\n"), - (Exit, ""), - (Element(Blankline), "\n"), + (Element(Inline), "a"), + (Exit(Leaf(Paragraph)), ""), + (Element(Blankline), ""), (Enter(Leaf(Heading { level: 2 })), "##"), - (Element(Inline), " hl\n"), - (Exit, "##"), - (Element(Blankline), "\n"), + (Element(Inline), "hl"), + (Exit(Leaf(Heading { level: 2 })), "##"), + (Element(Blankline), ""), (Enter(Leaf(Paragraph)), ""), - (Element(Inline), " para\n"), - (Exit, ""), - (Exit, ">"), + (Element(Inline), "para"), + (Exit(Leaf(Paragraph)), ""), + (Exit(Container(Blockquote)), ">"), ); } @@ -379,9 +379,13 @@ mod test { "```", // ), (Enter(Leaf(CodeBlock { fence_length: 3 })), "```"), - (Element(Inline), "\n"), - (Element(Inline), "l0\n"), - (Exit, "```"), + (Element(Inline), ""), + (Element(Inline), "l0"), + (Exit(Leaf(CodeBlock { fence_length: 3 })), "```"), + (Element(Blankline), "\n"), + (Enter(Leaf(Paragraph)), ""), + (Element(Inline), "para"), + (Exit(Leaf(Paragraph)), ""), ); test_parse!( concat!( @@ -395,8 +399,8 @@ mod test { (Element(Inline), "lang\n"), (Element(Inline), "l0\n"), (Element(Inline), "```\n"), - (Element(Inline), " l1\n"), - (Exit, "````"), + (Element(Inline), " l1"), + (Exit(Leaf(CodeBlock { fence_length: 4 })), "````"), ); } diff --git a/src/html.rs b/src/html.rs index 8b73547..2d86be1 100644 --- a/src/html.rs +++ b/src/html.rs @@ -1,6 +1,6 @@ use crate::Event; -pub fn push_html<'s, I: Iterator>(s: &mut String, events: I) { +pub fn push_html<'s, I: Iterator>>(s: &mut String, events: I) { Writer::new(events).write() } @@ -8,7 +8,7 @@ struct Writer { events: I, } -impl> Writer { +impl<'s, I: Iterator>> Writer { fn new(events: I) -> Self { Self { events } } diff --git a/src/inline.rs b/src/inline.rs index db38540..f531ecc 100644 --- a/src/inline.rs +++ b/src/inline.rs @@ -15,7 +15,7 @@ pub enum Atom { Escape, Nbsp, OpenMarker, // ?? - Ellipses, + Ellipsis, ImageMarker, // ?? EmDash, EnDash, diff --git a/src/lib.rs b/src/lib.rs index 56bedd2..91ecc3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,11 +12,11 @@ pub struct Block; const EOF: char = '\0'; #[derive(Debug, PartialEq, Eq)] -pub enum Event2<'s> { +pub enum Event<'s> { /// Start of a tag. - Start(TagKind<'s>, Attributes<'s>), + Start(Tag<'s>, Attributes<'s>), /// End of a tag. - End(TagKind<'s>), + End(Tag<'s>), /// A string object, text only. Str(&'s str), /// A verbatim string. @@ -38,16 +38,13 @@ pub enum Event2<'s> { /// A newline that may or may not break a line in the output format. Softbreak, /// A newline that must break a line. - HardBreak, + Hardbreak, + /// An escape character, not visible in output. + Escape, } -// Attributes are rare, better to pay 8 bytes always and sometimes an extra allocation instead of -// always 24 bytes. #[derive(Debug, PartialEq, Eq)] -pub struct Attributes<'s>(Option>>); - -#[derive(Debug, PartialEq, Eq)] -pub enum TagKind<'s> { +pub enum Tag<'s> { /// A paragraph. Paragraph, /// A heading. @@ -82,6 +79,24 @@ pub enum TagKind<'s> { DescriptionItem, /// A footnote definition. Footnote { tag: &'s str }, + /// A subscripted element. + Subscript, + /// A superscripted element. + Superscript, + /// An inserted element. + Insert, + /// A deleted element. + Delete, + /// An element emphasized with a bold typeface. + Strong, + /// An emphasized element. + Emphasis, + /// A highlighted inline element. + Mark, + /// An quoted element, using single quotes. + SingleQuoted, + /// A quoted inline element, using double quotes. + DoubleQuoted, } #[derive(Debug, PartialEq, Eq)] @@ -128,22 +143,96 @@ pub enum OrderedListFormat { ParenParen, } -/* impl<'s> Event<'s> { fn from_inline(src: &'s str, inline: inline::Event) -> Self { - match inline { - Enter + let content = inline.span.of(src); + match inline.kind { + inline::EventKind::Enter(c) | inline::EventKind::Exit(c) => { + let t = match c { + inline::Container::Span => Tag::Span, + inline::Container::Subscript => Tag::Subscript, + inline::Container::Superscript => Tag::Superscript, + inline::Container::Insert => Tag::Insert, + inline::Container::Delete => Tag::Delete, + inline::Container::Emphasis => Tag::Emphasis, + inline::Container::Strong => Tag::Strong, + inline::Container::Mark => Tag::Mark, + inline::Container::SingleQuoted => Tag::SingleQuoted, + inline::Container::DoubleQuoted => Tag::DoubleQuoted, + _ => todo!(), + }; + if matches!(inline.kind, inline::EventKind::Enter(_)) { + Self::Start(t, Attributes::none()) + } else { + Self::End(t) + } + } + inline::EventKind::Atom(a) => match a { + inline::Atom::Ellipsis => Self::Ellipsis, + inline::Atom::EnDash => Self::EnDash, + inline::Atom::EmDash => Self::EmDash, + inline::Atom::Nbsp => Self::NonBreakingSpace, + inline::Atom::Softbreak => Self::Softbreak, + inline::Atom::Hardbreak => Self::Hardbreak, + inline::Atom::Escape => Self::Escape, + _ => todo!(), + }, + inline::EventKind::Node(n) => match n { + inline::Node::Str => Self::Str(content), + inline::Node::Verbatim => Self::Verbatim(content), + inline::Node::InlineMath => Self::Math { + content, + display: false, + }, + inline::Node::DisplayMath => Self::Math { + content, + display: true, + }, + _ => todo!(), + }, } } } -*/ +impl<'s> Tag<'s> { + fn from_block(src: &'s str, block: block::Block) -> Self { + match block { + block::Block::Leaf(l) => match l { + block::Leaf::Paragraph => Self::Paragraph, + block::Leaf::Heading { level } => Self::Heading { level }, + block::Leaf::CodeBlock { .. } => Self::CodeBlock { language: None }, + _ => todo!(), + }, + block::Block::Container(c) => match c { + block::Container::Blockquote => Self::Blockquote, + block::Container::Div { .. } => Self::Div, + block::Container::Footnote { .. } => Self::Footnote { tag: todo!() }, + _ => todo!(), + }, + } + } +} + +// Attributes are rare, better to pay 8 bytes always and sometimes an extra allocation instead of +// always 24 bytes. #[derive(Debug, PartialEq, Eq)] -pub enum Event { - Start(block::Block), - End, - Inline(inline::Event), - Blankline, +pub struct Attributes<'s>(Option>>); + +impl<'s> Attributes<'s> { + #[must_use] + pub fn none() -> Self { + Self(None) + } + + #[must_use] + pub fn valid(src: &str) -> bool { + todo!() + } + + #[must_use] + pub fn parse(src: &'s str) -> Self { + todo!() + } } pub struct Parser<'s> { @@ -166,14 +255,14 @@ impl<'s> Parser<'s> { } impl<'s> Iterator for Parser<'s> { - type Item = Event; + type Item = Event<'s>; fn next(&mut self) -> Option { while let Some(parser) = &mut self.parser { // inside leaf block, with inline content if let Some(mut inline) = parser.next() { inline.span = inline.span.translate(self.inline_start); - return Some(Event::Inline(inline)); + return Some(Event::from_inline(self.src, inline)); } else if let Some(ev) = self.tree.next() { match ev.kind { tree::EventKind::Element(atom) => { @@ -181,9 +270,9 @@ impl<'s> Iterator for Parser<'s> { parser.parse(ev.span.of(self.src)); self.inline_start = ev.span.start(); } - tree::EventKind::Exit => { + tree::EventKind::Exit(block) => { self.parser = None; - return Some(Event::End); + return Some(Event::End(Tag::from_block(self.src, block))); } tree::EventKind::Enter(..) => unreachable!(), } @@ -199,29 +288,60 @@ impl<'s> Iterator for Parser<'s> { if matches!(block, block::Block::Leaf(..)) { self.parser = Some(inline::Parser::new()); } - Event::Start(block) + Event::Start(Tag::from_block(self.src, block), Attributes::none()) } - tree::EventKind::Exit => Event::End, + tree::EventKind::Exit(block) => Event::End(Tag::from_block(self.src, block)), }) } } #[cfg(test)] mod test { + use super::Attributes; use super::Event::*; - use crate::block::Block::*; - use crate::block::Container::*; - use crate::block::Leaf::*; - use crate::inline::Atom::*; - use crate::inline::EventKind::*; - use crate::inline::Node::*; + use super::Tag::*; macro_rules! test_parse { ($($st:ident,)? $src:expr $(,$($token:expr),* $(,)?)?) => { #[allow(unused)] let actual = super::Parser::new($src).collect::>(); let expected = &[$($($token),*,)?]; - assert_eq!(actual, expected, "\n\n{}\n\n", $src); + assert_eq!( + actual, + expected, + concat!( + "\n", + "\x1b[0;1m====================== INPUT =========================\x1b[0m\n", + "\x1b[2m{}", + "\x1b[0;1m================ ACTUAL vs EXPECTED ==================\x1b[0m\n", + "{}", + "\x1b[0;1m======================================================\x1b[0m\n", + ), + $src, + { + let a = actual.iter().map(|n| format!("{:?}", n)).collect::>(); + let b = expected.iter().map(|n| format!("{:?}", n)).collect::>(); + let max = a.len().max(b.len()); + let a_width = a.iter().map(|a| a.len()).max().unwrap_or(0); + a.iter() + .map(AsRef::as_ref) + .chain(std::iter::repeat("")) + .zip(b.iter().map(AsRef::as_ref).chain(std::iter::repeat(""))) + .take(max) + .map(|(a, b)| + format!( + "\x1b[{}m{:a_width$}\x1b[0m {}= \x1b[{}m{}\x1b[0m\n", + if a == b { "2" } else { "31" }, + a, + if a == b { '=' } else { '!' }, + if a == b { "2" } else { "32" }, + b, + a_width = a_width, + ) + ) + .collect::() + }, + ); }; } @@ -229,25 +349,25 @@ mod test { fn para() { test_parse!( "para", - Start(Leaf(Paragraph)), - Inline(Node(Str).span(0, 4)), - End + Start(Paragraph, Attributes::none()), + Str("para"), + End(Paragraph), ); test_parse!( "pa ra", - Start(Leaf(Paragraph)), - Inline(Node(Str).span(0, 9)), - End + Start(Paragraph, Attributes::none()), + Str("pa ra"), + End(Paragraph), ); test_parse!( "para0\n\npara1", - Start(Leaf(Paragraph)), - Inline(Node(Str).span(0, 6)), - End, + Start(Paragraph, Attributes::none()), + Str("para0\n"), + End(Paragraph), Blankline, - Start(Leaf(Paragraph)), - Inline(Node(Str).span(7, 12)), - End, + Start(Paragraph, Attributes::none()), + Str("para1"), + End(Paragraph), ); } } diff --git a/src/tree.rs b/src/tree.rs index 4af53d0..0c90f45 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -4,7 +4,7 @@ use crate::Span; pub enum EventKind { Enter(C), Element(E), - Exit, + Exit(C), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -53,10 +53,15 @@ impl Iterator for Tree { }; Some(Event { kind, span: n.span }) } else if let Some(block_ni) = self.branch.pop() { - let Node { next, span, .. } = &self.nodes[block_ni.index()]; + let Node { next, kind, span } = &self.nodes[block_ni.index()]; + let cont = if let NodeKind::Container(c, _) = kind { + c + } else { + panic!(); + }; self.head = *next; Some(Event { - kind: EventKind::Exit, + kind: EventKind::Exit(*cont), span: *span, }) } else { @@ -192,7 +197,7 @@ impl std::fmt::Display write!(f, "{}{}", indent, container)?; level += 1; } - EventKind::Exit => { + EventKind::Exit(_) => { level -= 1; continue; }