feat: dynamic block parsing

This commit is contained in:
PoiScript 2019-01-11 14:10:16 +08:00
parent 79477b812e
commit 38380aab2c
2 changed files with 225 additions and 388 deletions

View file

@ -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
))
)
}

View file

@ -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,23 +73,6 @@ 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,
);
}
// 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..]) {
@ -97,7 +82,7 @@ impl<'a> Element<'a> {
(
start,
Some(Element::Paragraph {
end: if pos == start { pos } else { pos - 1 },
end: pos - 1,
trailing: pos,
}),
Some((Element::FnDef(fd), off + 1)),
@ -110,11 +95,28 @@ impl<'a> Element<'a> {
pos = skip_space!(src, pos);
if pos <= src.len() {
macro_rules! ret {
($ele:expr, $off:expr) => {
return if pos == start {
($off, Some($ele), None)
} else {
(
start,
Some(Element::Paragraph {
end: end - 1,
trailing: pos - 1,
}),
Some(($ele, $off)),
)
};
};
}
if bytes[pos] == b'\n' {
return (
start,
Some(Element::Paragraph {
end: if pos == start { end } else { end - 1 },
end: end - 1,
trailing: pos,
}),
None,
@ -127,49 +129,14 @@ impl<'a> Element<'a> {
// 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)),
)
};
ret!(Element::Rule, off);
}
}
if bytes[pos] == b'#' {
if bytes[pos + 1] == b'+' {
if bytes[pos] == b'#' && 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((
match &src[pos + 8..pos + name] {
block_name if block_name.eq_ignore_ascii_case("CENTER") => ret!(
Element::CenterBlock {
args: if name == args {
None
@ -179,16 +146,10 @@ impl<'a> Element<'a> {
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 {
pos + args
),
block_name if block_name.eq_ignore_ascii_case("QUOTE") => ret!(
Element::QuoteBlock {
args: if name == args {
None
} else {
@ -196,213 +157,66 @@ impl<'a> Element<'a> {
},
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((
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..pos + content],
content: &src[pos + args + 1..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((
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..pos + content],
content: &src[pos + args + 1..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((
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..pos + content],
content: &src[pos + args + 1..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((
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..pos + content],
content: &src[pos + args + 1..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((
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..pos + content],
content: &src[pos + args + 1..pos + content],
},
args,
)),
)
}
}
block_name => {
if pos == start {
(
pos + args,
Some(Element::SpecialBlock {
pos + end
),
block_name => ret!(
Element::SpecialBlock {
name: block_name,
args: if name == args {
None
@ -411,74 +225,47 @@ impl<'a> Element<'a> {
},
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,
)),
)
}
}
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)),
)
};
ret!(Element::Keyword(kw), off)
}
}
// Comment
if src.as_bytes()[pos + 1] == b' ' {
if bytes[pos] == b'#' && 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)),
)
};
}
}
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,
);
}
}
}
}