From 79477b812ea9a2e54c6e9cd327a4750f5dce891c Mon Sep 17 00:00:00 2001 From: PoiScript Date: Fri, 11 Jan 2019 00:57:08 +0800 Subject: [PATCH] feat: block parsing --- STATUS.md | 2 +- src/elements/block.rs | 32 ++-- src/elements/dyn_block.rs | 0 src/elements/mod.rs | 337 +++++++++++++++++++++++++++++++++++++- src/parser.rs | 91 +++++++++- 5 files changed, 442 insertions(+), 20 deletions(-) create mode 100644 src/elements/dyn_block.rs diff --git a/STATUS.md b/STATUS.md index b089923..7b7d693 100644 --- a/STATUS.md +++ b/STATUS.md @@ -16,7 +16,7 @@ ## Elements - [ ] Babel Call -- [ ] Blocks +- [x] Blocks - [ ] Clock, Diary Sexp and Planning - [x] Comments - [ ] Fixed Width Areas diff --git a/src/elements/block.rs b/src/elements/block.rs index 80a64ed..a68b829 100644 --- a/src/elements/block.rs +++ b/src/elements/block.rs @@ -1,14 +1,24 @@ -pub enum BlockStart { - name: BlockName, +#[cfg_attr(test, derive(PartialEq, Debug))] +pub struct Block; + +impl Block { + pub fn parse(src: &str) -> Option<(usize, usize, usize, usize)> { + if src.len() < 17 || !src[0..8].eq_ignore_ascii_case("#+BEGIN_") { + return None; + } + + let args = eol!(src); + let name = until_while!(src, 8, |c| c == b' ' || c == b'\n', |c: u8| c + .is_ascii_alphabetic()); + // TODO: ignore case match + let content = src.find(&format!("\n#+END_{}", &src[8..name]))?; + let end = eol!(src, content + 1); + + Some((name, args, content, end + 1)) + } } -pub enum BlockName { - Center, - Comment, - Example, - Export, - Quote, - Src, - Verbose, - Special +#[test] +fn parse() { + // TODO: testing } diff --git a/src/elements/dyn_block.rs b/src/elements/dyn_block.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 15455db..9a57c02 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -1,7 +1,9 @@ +pub mod block; pub mod fn_def; pub mod keyword; pub mod rule; +pub use self::block::Block; pub use self::fn_def::FnDef; pub use self::keyword::Keyword; pub use self::rule::Rule; @@ -17,6 +19,42 @@ pub enum Element<'a> { Keyword(Keyword<'a>), FnDef(FnDef<'a>), + CenterBlock { + args: Option<&'a str>, + content_end: usize, + end: usize, + }, + QuoteBlock { + args: Option<&'a str>, + content_end: usize, + end: usize, + }, + SpecialBlock { + args: Option<&'a str>, + name: &'a str, + content_end: usize, + end: usize, + }, + CommentBlock { + content: &'a str, + args: Option<&'a str>, + }, + ExampleBlock { + content: &'a str, + args: Option<&'a str>, + }, + ExportBlock { + content: &'a str, + args: Option<&'a str>, + }, + SrcBlock { + content: &'a str, + args: Option<&'a str>, + }, + VerseBlock { + content: &'a str, + args: Option<&'a str>, + }, Rule, Comment(&'a str), } @@ -105,8 +143,305 @@ impl<'a> Element<'a> { } if bytes[pos] == b'#' { - // Keyword if bytes[pos + 1] == b'+' { + if let Some((name, args, content, end)) = Block::parse(&src[pos..]) { + // TODO: use macros + return match &src[pos + 8..pos + name] { + block_name if block_name.eq_ignore_ascii_case("CENTER") => { + if pos == start { + ( + pos + args, + Some(Element::CenterBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content_end: content + 1, + end, + }), + None, + ) + } else { + ( + start, + Some(Element::Paragraph { + end: if pos == start { end } else { end - 1 }, + trailing: pos - 1, + }), + Some(( + Element::CenterBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content_end: content + 1, + end, + }, + pos + args, + )), + ) + } + } + block_name if block_name.eq_ignore_ascii_case("QUOTE") => { + if pos == start { + ( + pos + args, + Some(Element::QuoteBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args].trim()) + }, + content_end: content + 1, + end, + }), + None, + ) + } else { + ( + start, + Some(Element::Paragraph { + end: if pos == start { end } else { end - 1 }, + trailing: pos - 1, + }), + Some(( + Element::QuoteBlock { + args: if name == args { + None + } else { + Some( + &src[pos + name..pos + args].trim(), + ) + }, + content_end: content + 1, + end, + }, + args + pos, + )), + ) + } + } + block_name if block_name.eq_ignore_ascii_case("COMMENT") => { + if pos == start { + ( + args, + Some(Element::CommentBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }), + None, + ) + } else { + ( + start, + Some(Element::Paragraph { + end: if pos == start { end } else { end - 1 }, + trailing: pos - 1, + }), + Some(( + Element::CommentBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }, + args, + )), + ) + } + } + block_name if block_name.eq_ignore_ascii_case("EXAMPLE") => { + if pos == start { + ( + args, + Some(Element::ExampleBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }), + None, + ) + } else { + ( + start, + Some(Element::Paragraph { + end: if pos == start { end } else { end - 1 }, + trailing: pos - 1, + }), + Some(( + Element::ExampleBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }, + args, + )), + ) + } + } + block_name if block_name.eq_ignore_ascii_case("EXPORT") => { + if pos == start { + ( + args, + Some(Element::ExportBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }), + None, + ) + } else { + ( + start, + Some(Element::Paragraph { + end: if pos == start { end } else { end - 1 }, + trailing: pos - 1, + }), + Some(( + Element::ExportBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }, + args, + )), + ) + } + } + block_name if block_name.eq_ignore_ascii_case("SRC") => { + if pos == start { + ( + args, + Some(Element::SrcBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }), + None, + ) + } else { + ( + start, + Some(Element::Paragraph { + end: if pos == start { end } else { end - 1 }, + trailing: pos - 1, + }), + Some(( + Element::SrcBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }, + args, + )), + ) + } + } + block_name if block_name.eq_ignore_ascii_case("VERSE") => { + if pos == start { + ( + args, + Some(Element::VerseBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }), + None, + ) + } else { + ( + start, + Some(Element::Paragraph { + end: if pos == start { end } else { end - 1 }, + trailing: pos - 1, + }), + Some(( + Element::VerseBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args..pos + content], + }, + args, + )), + ) + } + } + block_name => { + if pos == start { + ( + pos + args, + Some(Element::SpecialBlock { + name: block_name, + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args].trim()) + }, + content_end: content + 1, + end, + }), + None, + ) + } else { + ( + start, + Some(Element::Paragraph { + end: if pos == start { end } else { end - 1 }, + trailing: pos - 1, + }), + Some(( + Element::SpecialBlock { + name: block_name, + args: if name == args { + None + } else { + Some( + &src[pos + name..pos + args].trim(), + ) + }, + content_end: content + 1, + end, + }, + pos + args, + )), + ) + } + } + }; + } + if let Some((kw, off)) = Keyword::parse(&src[pos..]) { return if pos == start { (off, Some(Element::Keyword(kw)), None) diff --git a/src/parser.rs b/src/parser.rs index ff5f32c..1c71d7b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,8 +9,10 @@ pub enum Container { Section { end: usize }, Paragraph { end: usize, trailing: usize }, + CenterBlock { content_end: usize, end: usize }, + QuoteBlock { content_end: usize, end: usize }, + SpecialBlock { content_end: usize, end: usize }, - Block, Drawer, LatexEnv, List, @@ -33,8 +35,37 @@ pub enum Event<'a> { StartParagraph, EndParagraph, - BlockStart, - BlockEnd, + StartCenterBlock, + EndCenterBlock, + StartQuoteBlock, + EndQuoteBlock, + StartSpecialBlock { + name: &'a str, + args: Option<&'a str>, + }, + EndSpecialBlock, + + CommentBlock { + content: &'a str, + args: Option<&'a str>, + }, + ExampleBlock { + content: &'a str, + args: Option<&'a str>, + }, + ExportBlock { + content: &'a str, + args: Option<&'a str>, + }, + SrcBlock { + content: &'a str, + args: Option<&'a str>, + }, + VerseBlock { + content: &'a str, + args: Option<&'a str>, + }, + DynBlockStart, DynBlockEnd, ListStart, @@ -136,11 +167,30 @@ impl<'a> Parser<'a> { self.off += off; if let Some(ele) = ele { - if let Element::Paragraph { end, trailing } = ele { - self.stack.push(Container::Paragraph { + match ele { + Element::Paragraph { end, trailing } => self.stack.push(Container::Paragraph { end: end + self.off - off, trailing: trailing + self.off - off, - }); + }), + Element::QuoteBlock { + end, content_end, .. + } => self.stack.push(Container::QuoteBlock { + content_end: content_end + self.off - off, + end: end + self.off - off, + }), + Element::CenterBlock { + end, content_end, .. + } => self.stack.push(Container::CenterBlock { + content_end: content_end + self.off - off, + end: end + self.off - off, + }), + Element::SpecialBlock { + end, content_end, .. + } => self.stack.push(Container::SpecialBlock { + content_end: content_end + self.off - off, + end: end + self.off - off, + }), + _ => (), } ele.into() } else { @@ -187,6 +237,9 @@ impl<'a> Parser<'a> { Container::Headline { .. } => Event::EndHeadline, Container::Italic { .. } => Event::EndItalic, Container::Bold { .. } => Event::EndBold, + Container::CenterBlock { .. } => Event::EndCenterBlock, + Container::QuoteBlock { .. } => Event::EndQuoteBlock, + Container::SpecialBlock { .. } => Event::EndSpecialBlock, _ => unimplemented!(), } } @@ -205,7 +258,7 @@ impl<'a> Iterator for Parser<'a> { Some(self.start_section_or_headline(tail)) } } else { - let last = *self.stack.last_mut()?; + let last = *self.stack.last_mut().unwrap(); Some(match last { Container::Headline { beg, end } => { @@ -217,6 +270,22 @@ impl<'a> Iterator for Parser<'a> { self.start_headline(tail) } } + Container::CenterBlock { + content_end, end, .. + } + | Container::QuoteBlock { + content_end, end, .. + } + | Container::SpecialBlock { + content_end, end, .. + } => { + if self.off >= content_end { + self.off = end; + self.end() + } else { + self.next_ele(content_end) + } + } Container::Section { end } => { if self.off >= end { self.end() @@ -280,6 +349,14 @@ impl<'a> From> for Event<'a> { Element::Keyword(kw) => Event::Keyword(kw), Element::Paragraph { .. } => Event::StartParagraph, Element::Rule => Event::Rule, + Element::CenterBlock { .. } => Event::StartCenterBlock, + Element::QuoteBlock { .. } => Event::StartQuoteBlock, + Element::SpecialBlock { name, args, .. } => Event::StartSpecialBlock { name, args }, + Element::CommentBlock { args, content } => Event::CommentBlock { args, content }, + Element::ExampleBlock { args, content } => Event::ExampleBlock { args, content }, + Element::ExportBlock { args, content } => Event::ExportBlock { args, content }, + Element::SrcBlock { args, content } => Event::SrcBlock { args, content }, + Element::VerseBlock { args, content } => Event::VerseBlock { args, content }, } } }