From 38380aab2cd204ee2eb68898625a421df5a4e36b Mon Sep 17 00:00:00 2001 From: PoiScript Date: Fri, 11 Jan 2019 14:10:16 +0800 Subject: [PATCH] feat: dynamic block parsing --- src/elements/dyn_block.rs | 50 ++++ src/elements/mod.rs | 563 ++++++++++++-------------------------- 2 files changed, 225 insertions(+), 388 deletions(-) diff --git a/src/elements/dyn_block.rs b/src/elements/dyn_block.rs index e69de29..e748542 100644 --- a/src/elements/dyn_block.rs +++ b/src/elements/dyn_block.rs @@ -0,0 +1,50 @@ +#[cfg_attr(test, derive(PartialEq, Debug))] +pub struct DynBlock<'a> { + pub name: &'a str, + pub para: &'a str, +} + +impl<'a> DynBlock<'a> { + pub fn parse(src: &'a str) -> Option<(DynBlock<'a>, usize, usize)> { + if src.len() < 17 || !src[0..9].eq_ignore_ascii_case("#+BEGIN: ") { + return None; + } + + let args = eol!(src); + let name = until_while!(src, 9, |c| c == b' ' || c == b'\n', |c: u8| c + .is_ascii_alphabetic()); + // TODO: ignore case matching + let content = src.find("\n#+END:")?; + let end = eol!(src, content + 1); + + Some(( + DynBlock { + name: &src[9..name], + para: &src[name..args].trim(), + }, + content, + end, + )) + } +} + +#[test] +fn parse() { + // TODO: testing + assert_eq!( + DynBlock::parse( + r"#+BEGIN: clocktable :scope file :block yesterday +CONTENTS +#+END: +" + ), + Some(( + DynBlock { + name: "clocktable", + para: ":scope file :block yesterday" + }, + 57, + 64 + )) + ) +} diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 9a57c02..51a2018 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -1,9 +1,11 @@ pub mod block; +pub mod dyn_block; pub mod fn_def; pub mod keyword; pub mod rule; pub use self::block::Block; +pub use self::dyn_block::DynBlock; pub use self::fn_def::FnDef; pub use self::keyword::Keyword; pub use self::rule::Rule; @@ -71,414 +73,199 @@ impl<'a> Element<'a> { } loop { - if pos >= src.len() { - return ( - start, - Some(Element::Paragraph { - end: if bytes[pos - 1] == b'\n' { - pos - 1 - } else { - pos - }, - trailing: pos, - }), - None, - ); + // Unlike other element, footnote definition must starts at column 0 + if bytes[pos] == b'[' { + if let Some((fd, off)) = FnDef::parse(&src[pos..]) { + return if pos == start { + (off + 1, Some(Element::FnDef(fd)), None) + } else { + ( + start, + Some(Element::Paragraph { + end: pos - 1, + trailing: pos, + }), + Some((Element::FnDef(fd), off + 1)), + ) + }; + } } - // TODO: refactor with src[..].find('\n') - if pos == start || bytes[pos - 1] == b'\n' { - // Unlike other element, footnote definition must starts at column 0 - if bytes[pos] == b'[' { - if let Some((fd, off)) = FnDef::parse(&src[pos..]) { + let end = pos; + pos = skip_space!(src, pos); + + if pos <= src.len() { + macro_rules! ret { + ($ele:expr, $off:expr) => { return if pos == start { - (off + 1, Some(Element::FnDef(fd)), None) + ($off, Some($ele), None) } else { ( start, Some(Element::Paragraph { - end: if pos == start { pos } else { pos - 1 }, - trailing: pos, + end: end - 1, + trailing: pos - 1, }), - Some((Element::FnDef(fd), off + 1)), + Some(($ele, $off)), ) }; + }; + } + + if bytes[pos] == b'\n' { + return ( + start, + Some(Element::Paragraph { + end: end - 1, + trailing: pos, + }), + None, + ); + } + + // TODO: LaTeX environment + if bytes[pos] == b'\\' {} + + // Rule + if bytes[pos] == b'-' { + if let Some(off) = Rule::parse(&src[pos..]) { + ret!(Element::Rule, off); } } - let end = pos; - pos = skip_space!(src, pos); - - if pos <= src.len() { - if bytes[pos] == b'\n' { - return ( - start, - Some(Element::Paragraph { - end: if pos == start { end } else { end - 1 }, - trailing: pos, - }), - None, - ); + if bytes[pos] == b'#' && bytes[pos + 1] == b'+' { + if let Some((name, args, content, end)) = Block::parse(&src[pos..]) { + match &src[pos + 8..pos + name] { + block_name if block_name.eq_ignore_ascii_case("CENTER") => ret!( + 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") => ret!( + Element::QuoteBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args].trim()) + }, + content_end: content + 1, + end, + }, + pos + args + ), + block_name if block_name.eq_ignore_ascii_case("COMMENT") => ret!( + Element::CommentBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args + 1..pos + content], + }, + pos + end + ), + block_name if block_name.eq_ignore_ascii_case("EXAMPLE") => ret!( + Element::ExampleBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args + 1..pos + content], + }, + pos + end + ), + block_name if block_name.eq_ignore_ascii_case("EXPORT") => ret!( + Element::ExportBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args + 1..pos + content], + }, + pos + end + ), + block_name if block_name.eq_ignore_ascii_case("SRC") => ret!( + Element::SrcBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args + 1..pos + content], + }, + pos + end + ), + block_name if block_name.eq_ignore_ascii_case("VERSE") => ret!( + Element::VerseBlock { + args: if name == args { + None + } else { + Some(&src[pos + name..pos + args]) + }, + content: &src[pos + args + 1..pos + content], + }, + pos + end + ), + block_name => ret!( + 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 + ), + }; } - // TODO: LaTeX environment - if bytes[pos] == b'\\' {} - - // Rule - if bytes[pos] == b'-' { - if let Some(off) = Rule::parse(&src[pos..]) { - return if pos == start { - (off, Some(Element::Rule), None) - } else { - ( - start, - Some(Element::Paragraph { - end: if pos == start { end } else { end - 1 }, - trailing: pos, - }), - Some((Element::Rule, off)), - ) - }; - } + if let Some((kw, off)) = Keyword::parse(&src[pos..]) { + ret!(Element::Keyword(kw), off) } + } - if bytes[pos] == b'#' { - 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) - } else { - ( - start, - Some(Element::Paragraph { - end: if pos == start { end } else { end - 1 }, - trailing: pos - 1, - }), - Some((Element::Keyword(kw), off)), - ) - }; - } - } - - // Comment - if src.as_bytes()[pos + 1] == b' ' { - let eol = eol!(src, pos); - return if pos == start { - (eol, Some(Element::Comment(&src[pos + 1..eol])), None) - } else { - ( - start, - Some(Element::Paragraph { - end: if pos == start { end } else { end - 1 }, - trailing: pos - 1, - }), - Some((Element::Comment(&src[pos + 1..eol]), eol)), - ) - }; - } - } + // Comment + if bytes[pos] == b'#' && bytes[pos + 1] == b' ' { + let eol = eol!(src, pos); + ret!(Element::Comment(&src[pos + 1..eol]), eol); } } - pos += 1 + if let Some(off) = &src[pos..].find('\n') { + pos += off + 1; + // last char + if pos == src.len() { + return ( + start, + Some(Element::Paragraph { + end: pos - 1, + trailing: pos, + }), + None, + ); + } + } else { + return ( + start, + Some(Element::Paragraph { + end: src.len(), + trailing: src.len(), + }), + None, + ); + } } } }