refactor: clenup

This commit is contained in:
PoiScript 2019-02-09 20:32:31 +08:00
parent c5b14256f0
commit 4d56633c43
7 changed files with 432 additions and 492 deletions

View file

@ -1,7 +1,7 @@
use crate::lines::Lines; use crate::lines::Lines;
use memchr::memchr2; use memchr::memchr2;
// return (name, parameters, contents-begin, contents-end, end) /// return (name, parameters, contents-begin, contents-end, end)
#[inline] #[inline]
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> { pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
debug_assert!(src.starts_with("#+")); debug_assert!(src.starts_with("#+"));
@ -11,7 +11,10 @@ pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
} }
let bytes = src.as_bytes(); let bytes = src.as_bytes();
let args = eol!(src);
let args = memchr::memchr(b'\n', src.as_bytes())
.map(|i| i + 1)
.unwrap_or_else(|| src.len());
let name = memchr2(b' ', b'\n', &bytes[9..]) let name = memchr2(b' ', b'\n', &bytes[9..])
.map(|i| i + 9) .map(|i| i + 9)
.filter(|&i| { .filter(|&i| {
@ -56,7 +59,7 @@ CONTENTS
#+END: #+END:
" "
), ),
Some(("clocktable", Some(":scope file"), 31, 40, 48)) Some(("clocktable", Some(":scope file"), 32, 40, 48))
) );
} }
} }

View file

@ -11,7 +11,7 @@ pub fn parse(src: &str) -> Option<(&str, &str, usize)> {
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_') .all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})?; })?;
let end = eol!(src); let end = memchr::memchr(b'\n', src.as_bytes()).unwrap_or_else(|| src.len());
Some((&src[4..label], &src[label + 1..end], end)) Some((&src[4..label], &src[label + 1..end], end))
} }

View file

@ -7,8 +7,7 @@ pub mod rule;
pub use self::keyword::Key; pub use self::keyword::Key;
use memchr::memchr; use memchr::{memchr, memchr_iter};
use memchr::memchr_iter;
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)] #[derive(Debug)]
@ -79,68 +78,26 @@ pub enum Element<'a> {
}, },
} }
impl<'a> Element<'a> { // return (element, off, next element, next offset)
// return (element, off, next element, next offset) // the end of first element is relative to the offset
// the end of first element is relative to the offset // next offset is relative to the end of the first element
// next offset is relative to the end of the first element pub fn parse<'a>(src: &'a str) -> (Option<Element<'a>>, usize, Option<(Element<'a>, usize)>) {
pub fn next_2(src: &'a str) -> (Option<Element<'a>>, usize, Option<(Element<'a>, usize)>) { // skip empty lines
// skip empty lines let mut pos = match src.chars().position(|c| c != '\n') {
let mut pos = match src.chars().position(|c| c != '\n') { Some(pos) => pos,
Some(pos) => pos, None => return (None, src.len(), None),
None => return (None, src.len(), None), };
}; let start = pos;
let start = pos; let bytes = src.as_bytes();
let bytes = src.as_bytes(); let mut line_ends = memchr_iter(b'\n', &bytes[start..]).map(|i| i + start);
let mut line_ends = memchr_iter(b'\n', &bytes[start..]).map(|i| i + start);
loop { loop {
let line_beg = pos; let line_beg = pos;
macro_rules! brk { macro_rules! brk {
($ele:expr, $off:expr) => { ($ele:expr, $off:expr) => {
break if line_beg == 0 || pos == start { break if line_beg == 0 || pos == start {
(Some($ele), start + $off, None) (Some($ele), start + $off, None)
} else {
(
Some(Element::Paragraph {
cont_end: line_beg - start - 1,
end: line_beg - start,
}),
start,
Some(($ele, $off)),
)
};
};
}
// Unlike other element, footnote def must starts at column 0
if bytes[pos..].starts_with(b"[fn:") {
if let Some((label, cont, off)) = fn_def::parse(&src[pos..]) {
brk!(Element::FnDef { label, cont }, off + 1);
}
}
if bytes[pos] == b'\n' {
break (
Some(Element::Paragraph {
cont_end: pos - start - 1,
end: pos - start + 1,
}),
start,
None,
);
}
pos = skip_space!(src, pos);
let (is_item, ordered) = list::is_item(&src[pos..]);
if is_item {
let list = Element::List {
ident: pos - line_beg,
ordered,
};
break if line_beg == start {
(Some(list), start, None)
} else { } else {
( (
Some(Element::Paragraph { Some(Element::Paragraph {
@ -148,245 +105,289 @@ impl<'a> Element<'a> {
end: line_beg - start, end: line_beg - start,
}), }),
start, start,
Some((list, 0)), Some(($ele, $off)),
) )
}; };
};
}
// Unlike other element, footnote def must starts at column 0
if bytes[pos..].starts_with(b"[fn:") {
if let Some((label, cont, off)) = fn_def::parse(&src[pos..]) {
brk!(Element::FnDef { label, cont }, off + 1);
}
}
if bytes[pos] == b'\n' {
break (
Some(Element::Paragraph {
cont_end: pos - start - 1,
end: pos - start + 1,
}),
start,
None,
);
}
pos = skip_space!(src, pos);
let (is_item, ordered) = list::is_item(&src[pos..]);
if is_item {
let list = Element::List {
ident: pos - line_beg,
ordered,
};
break if line_beg == start {
(Some(list), start, None)
} else {
(
Some(Element::Paragraph {
cont_end: line_beg - start - 1,
end: line_beg - start,
}),
start,
Some((list, 0)),
)
};
}
// TODO: LaTeX environment
if bytes[pos..].starts_with(b"\\begin{") {}
// Rule
if bytes[pos] == b'-' {
let off = rule::parse(&src[pos..]);
if off != 0 {
brk!(Element::Rule, off);
}
}
// TODO: multiple lines fixed width area
if bytes[pos..].starts_with(b": ") || bytes[pos..].starts_with(b":\n") {
let eol = memchr(b'\n', &bytes[pos..])
.map(|i| i + 1)
.unwrap_or_else(|| src.len() - pos);
brk!(Element::FixedWidth(&src[pos + 1..pos + eol].trim()), eol);
}
if bytes[pos..].starts_with(b"#+") {
if let Some((name, args, cont_beg, cont_end, end)) = block::parse(&src[pos..]) {
let cont = &src[pos + cont_beg..pos + cont_end];
match name.to_uppercase().as_str() {
"COMMENT" => brk!(Element::CommentBlock { args, cont }, end),
"EXAMPLE" => brk!(Element::ExampleBlock { args, cont }, end),
"EXPORT" => brk!(Element::ExportBlock { args, cont }, end),
"SRC" => brk!(Element::SrcBlock { args, cont }, end),
"VERSE" => brk!(Element::VerseBlock { args, cont }, end),
"CENTER" => brk!(
Element::CtrBlock {
args,
cont_end: cont_end - cont_beg,
end: end - cont_beg,
},
cont_beg
),
"QUOTE" => brk!(
Element::QteBlock {
args,
cont_end: cont_end - cont_beg,
end: end - cont_beg,
},
cont_beg
),
_ => brk!(
Element::SplBlock {
name,
args,
cont_end: cont_end - cont_beg,
end: end - cont_beg,
},
cont_beg
),
};
} }
// TODO: LaTeX environment if let Some((name, args, cont_beg, cont_end, end)) = dyn_block::parse(&src[pos..]) {
if bytes[pos..].starts_with(b"\\begin{") {} brk!(
Element::DynBlock {
// Rule name,
if bytes[pos] == b'-' { args,
let off = rule::parse(&src[pos..]); cont_end: cont_end - cont_beg,
if off != 0 { end: end - cont_beg,
brk!(Element::Rule, off); },
} cont_beg
)
} }
// TODO: multiple lines fixed width area if let Some((key, value, off)) = keyword::parse(&src[pos..]) {
if bytes[pos..].starts_with(b": ") || bytes[pos..].starts_with(b":\n") { brk!(
let eol = memchr(b'\n', &bytes[pos..]) if let Key::Call = key {
.map(|i| i + 1) Element::Call { value }
.unwrap_or_else(|| src.len() - pos); } else {
brk!(Element::FixedWidth(&src[pos + 1..pos + eol].trim()), eol); Element::Keyword { key, value }
},
off
)
} }
}
if bytes[pos..].starts_with(b"#+") { // Comment
if let Some((name, args, cont_beg, cont_end, end)) = block::parse(&src[pos..]) { // TODO: multiple lines comment
let cont = &src[pos + cont_beg..pos + cont_end]; if bytes[pos..].starts_with(b"# ") || bytes[pos..].starts_with(b"#\n") {
match name.to_uppercase().as_str() { let eol = memchr(b'\n', &bytes[pos..])
"COMMENT" => brk!(Element::CommentBlock { args, cont }, end), .map(|i| i + 1)
"EXAMPLE" => brk!(Element::ExampleBlock { args, cont }, end), .unwrap_or_else(|| src.len() - pos);
"EXPORT" => brk!(Element::ExportBlock { args, cont }, end), brk!(Element::Comment(&src[pos + 1..pos + eol].trim()), eol);
"SRC" => brk!(Element::SrcBlock { args, cont }, end), }
"VERSE" => brk!(Element::VerseBlock { args, cont }, end),
"CENTER" => brk!(
Element::CtrBlock {
args,
cont_end: cont_end - cont_beg,
end: end - cont_beg,
},
cont_beg
),
"QUOTE" => brk!(
Element::QteBlock {
args,
cont_end: cont_end - cont_beg,
end: end - cont_beg,
},
cont_beg
),
_ => brk!(
Element::SplBlock {
name,
args,
cont_end: cont_end - cont_beg,
end: end - cont_beg,
},
cont_beg
),
};
}
if let Some((name, args, cont_beg, cont_end, end)) = dyn_block::parse(&src[pos..]) { // move to the beginning of the next line
brk!( if let Some(off) = line_ends.next() {
Element::DynBlock { pos = off + 1;
name,
args,
cont_end,
end,
},
cont_beg
)
}
if let Some((key, value, off)) = keyword::parse(&src[pos..]) { // the last character
brk!( if pos >= src.len() {
if let Key::Call = key {
Element::Call { value }
} else {
Element::Keyword { key, value }
},
off
)
}
}
// Comment
// TODO: multiple lines comment
if bytes[pos..].starts_with(b"# ") || bytes[pos..].starts_with(b"#\n") {
let eol = memchr(b'\n', &bytes[pos..])
.map(|i| i + 1)
.unwrap_or_else(|| src.len() - pos);
brk!(Element::Comment(&src[pos + 1..pos + eol].trim()), eol);
}
// move to the beginning of the next line
if let Some(off) = line_ends.next() {
pos = off + 1;
// the last character
if pos >= src.len() {
break (
Some(Element::Paragraph {
cont_end: src.len() - start - 1,
end: src.len() - start,
}),
start,
None,
);
}
} else {
break ( break (
Some(Element::Paragraph { Some(Element::Paragraph {
cont_end: src.len() - start, cont_end: src.len() - start - 1,
end: src.len() - start, end: src.len() - start,
}), }),
start, start,
None, None,
); );
} }
} else {
break (
Some(Element::Paragraph {
cont_end: src.len() - start,
end: src.len() - start,
}),
start,
None,
);
} }
} }
} }
#[test] #[cfg(test)]
fn next_2() { mod tests {
use self::Element::*; #[test]
fn parse() {
use super::parse;
use super::Element::*;
assert_eq!(Element::next_2("\n\n\n"), (None, 3, None)); assert_eq!(parse("\n\n\n"), (None, 3, None));
let len = "Lorem ipsum dolor sit amet.".len(); let len = "Lorem ipsum dolor sit amet.".len();
assert_eq!( assert_eq!(
Element::next_2("\nLorem ipsum dolor sit amet.\n\n\n"), parse("\nLorem ipsum dolor sit amet.\n\n\n"),
( (
Some(Paragraph { Some(Paragraph {
cont_end: len, cont_end: len,
end: len + 2, end: len + 2,
}), }),
1, 1,
None None
) )
); );
assert_eq!( assert_eq!(
Element::next_2("\n\nLorem ipsum dolor sit amet.\n\n"), parse("\n\nLorem ipsum dolor sit amet.\n\n"),
( (
Some(Paragraph { Some(Paragraph {
cont_end: len, cont_end: len,
end: len + 2, end: len + 2,
}), }),
2, 2,
None None
) )
); );
assert_eq!( assert_eq!(
Element::next_2("\nLorem ipsum dolor sit amet.\n"), parse("\nLorem ipsum dolor sit amet.\n"),
( (
Some(Paragraph { Some(Paragraph {
cont_end: len, cont_end: len,
end: len + 1, end: len + 1,
}), }),
1, 1,
None None
) )
); );
assert_eq!( assert_eq!(
Element::next_2("\n\n\nLorem ipsum dolor sit amet."), parse("\n\n\nLorem ipsum dolor sit amet."),
( (
Some(Paragraph { Some(Paragraph {
cont_end: len, cont_end: len,
end: len, end: len,
}), }),
3, 3,
None None
) )
); );
assert_eq!( assert_eq!(
Element::next_2("\n\n\n: Lorem ipsum dolor sit amet.\n"), parse("\n\n\n: Lorem ipsum dolor sit amet.\n"),
( (
Some(FixedWidth("Lorem ipsum dolor sit amet.")), Some(FixedWidth("Lorem ipsum dolor sit amet.")),
"\n\n\n: Lorem ipsum dolor sit amet.\n".len(), "\n\n\n: Lorem ipsum dolor sit amet.\n".len(),
None None
) )
); );
assert_eq!( assert_eq!(
Element::next_2("\n\n\n: Lorem ipsum dolor sit amet."), parse("\n\n\n: Lorem ipsum dolor sit amet."),
( (
Some(FixedWidth("Lorem ipsum dolor sit amet.")), Some(FixedWidth("Lorem ipsum dolor sit amet.")),
"\n\n\n: Lorem ipsum dolor sit amet.".len(), "\n\n\n: Lorem ipsum dolor sit amet.".len(),
None None
) )
); );
assert_eq!( assert_eq!(
Element::next_2("\n\nLorem ipsum dolor sit amet.\n: Lorem ipsum dolor sit amet.\n"), parse("\n\nLorem ipsum dolor sit amet.\n: Lorem ipsum dolor sit amet.\n"),
( (
Some(Paragraph { Some(Paragraph {
cont_end: len, cont_end: len,
end: len + 1, end: len + 1,
}), }),
2, 2,
Some((FixedWidth("Lorem ipsum dolor sit amet."), 30)) Some((FixedWidth("Lorem ipsum dolor sit amet."), 30))
) )
); );
assert_eq!( assert_eq!(
Element::next_2("\n\nLorem ipsum dolor sit amet.\n+ Lorem ipsum dolor sit amet.\n"), parse("\n\nLorem ipsum dolor sit amet.\n+ Lorem ipsum dolor sit amet.\n"),
( (
Some(Paragraph { Some(Paragraph {
cont_end: len, cont_end: len,
end: len + 1, end: len + 1,
}), }),
2, 2,
Some(( Some((
List { List {
ident: 0, ident: 0,
ordered: false, ordered: false,
}, },
0 0
)) ))
) )
); );
assert_eq!( assert_eq!(
Element::next_2("\n\nLorem ipsum dolor sit amet.\n#+BEGIN_QUOTE\nLorem ipsum dolor sit amet.\n#+END_QUOTE\n"), parse("\n\nLorem ipsum dolor sit amet.\n#+BEGIN_QUOTE\nLorem ipsum dolor sit amet.\n#+END_QUOTE\n"),
( (
Some(Paragraph { Some(Paragraph {
cont_end: len, cont_end: len,
end: len + 1, end: len + 1,
}), }),
2, 2,
Some(( Some((
QteBlock { QteBlock {
args: None, args: None,
cont_end: len + 1, cont_end: len + 1,
end: len + 1 + "#+END_QUOTE\n".len() end: len + 1 + "#+END_QUOTE\n".len()
}, },
"#+BEGIN_QUOTE\n".len() "#+BEGIN_QUOTE\n".len()
)) ))
) )
); );
// TODO: more tests // TODO: more tests
}
} }

View file

@ -1,5 +1,7 @@
//! Headline //! Headline
use memchr::memchr2;
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)] #[derive(Debug)]
pub struct Headline<'a> { pub struct Headline<'a> {
@ -34,20 +36,12 @@ impl<'a> Headline<'a> {
#[inline] #[inline]
fn parse_keyword(src: &'a str) -> Option<(&'a str, usize)> { fn parse_keyword(src: &'a str) -> Option<(&'a str, usize)> {
let mut pos = 0; let pos = memchr2(b' ', b'\n', src.as_bytes()).unwrap_or_else(|| src.len());
while pos < src.len() { let word = &src[0..pos];
if src.as_bytes()[pos] == b' ' { if word.as_bytes().iter().all(|&c| c.is_ascii_uppercase()) && word != "COMMENT" {
break; Some((word, pos))
} else if src.as_bytes()[pos].is_ascii_uppercase() {
pos += 1;
} else {
return None;
}
}
if pos == src.len() || src[0..pos] == *"COMMENT" {
None
} else { } else {
Some((&src[0..pos], pos)) None
} }
} }
@ -80,21 +74,13 @@ impl<'a> Headline<'a> {
/// assert_eq!(hdl.keyword, Some("DONE")); /// assert_eq!(hdl.keyword, Some("DONE"));
/// ``` /// ```
pub fn parse(src: &'a str) -> (Headline<'a>, usize, usize) { pub fn parse(src: &'a str) -> (Headline<'a>, usize, usize) {
let mut level = 0; let level = memchr2(b'\n', b' ', src.as_bytes()).unwrap_or_else(|| src.len());
loop {
if src.as_bytes()[level] == b'*' {
level += 1;
} else {
break;
}
}
let eol = eol!(src); debug_assert!(src.as_bytes()[0..level].iter().all(|&c| c == b'*'));
let end = if eol == src.len() {
eol let (eol, end) = memchr::memchr(b'\n', src.as_bytes())
} else { .map(|i| (i, Headline::find_level(&src[i..], level) + i))
Headline::find_level(&src[eol..], level) + eol .unwrap_or_else(|| (src.len(), src.len()));
};
let mut title_start = skip_space!(src, level); let mut title_start = skip_space!(src, level);
@ -129,12 +115,11 @@ impl<'a> Headline<'a> {
pub fn find_level(src: &str, level: usize) -> usize { pub fn find_level(src: &str, level: usize) -> usize {
use jetscii::ByteSubstring; use jetscii::ByteSubstring;
use memchr::memchr2;
let bytes = src.as_bytes(); let bytes = src.as_bytes();
if bytes[0] == b'*' { if bytes[0] == b'*' {
if let Some(stars) = memchr2(b'\n', b' ', bytes) { if let Some(stars) = memchr2(b'\n', b' ', bytes) {
if stars > 0 && stars <= level && bytes[0..stars].iter().all(|&c| c == b'*') { if stars <= level && bytes[0..stars].iter().all(|&c| c == b'*') {
return 0; return 0;
} }
} }

View file

@ -68,152 +68,133 @@ pub enum Object<'a> {
Text(&'a str), Text(&'a str),
} }
impl<'a> Object<'a> { pub fn parse<'a>(src: &'a str) -> (Object<'a>, usize, Option<(Object<'a>, usize)>) {
pub fn next_2(src: &'a str) -> (Object<'a>, usize, Option<(Object<'a>, usize)>) { let bytes = src.as_bytes();
let bytes = src.as_bytes();
if src.len() <= 2 { if src.len() <= 2 {
return (Object::Text(src), src.len(), None); return (Object::Text(src), src.len(), None);
}
let bs = bytes!(b'@', b' ', b'"', b'(', b'\n', b'{', b'<', b'[');
let mut pos = 0;
loop {
macro_rules! brk {
($obj:expr, $off:expr, $pos:expr) => {
break if $pos == 0 {
($obj, $off, None)
} else {
(Object::Text(&src[0..$pos]), $pos, Some(($obj, $off)))
};
};
} }
let bs = bytes!(b'@', b' ', b'"', b'(', b'\n', b'{', b'<', b'['); match bytes[pos] {
b'@' if bytes[pos + 1] == b'@' => {
let mut pos = 0; if let Some((name, value, off)) = snippet::parse(&src[pos..]) {
loop { brk!(Object::Snippet { name, value }, off, pos);
macro_rules! brk { }
($obj:expr, $off:expr, $pos:expr) => {
break if $pos == 0 {
($obj, $off, None)
} else {
(Object::Text(&src[0..$pos]), $pos, Some(($obj, $off)))
};
};
} }
b'{' if bytes[pos + 1] == b'{' && bytes[pos + 2] == b'{' => {
let mut pre = pos; if let Some((name, args, off)) = macros::parse(&src[pos..]) {
brk!(Object::Macros { name, args }, off, pos);
match bytes[pos] {
b'@' if bytes[pos + 1] == b'@' => {
if let Some((name, value, off)) = snippet::parse(&src[pos..]) {
brk!(Object::Snippet { name, value }, off, pos);
}
} }
b'{' if bytes[pos + 1] == b'{' && bytes[pos + 2] == b'{' => {
if let Some((name, args, off)) = macros::parse(&src[pos..]) {
brk!(Object::Macros { name, args }, off, pos);
}
}
b'<' if bytes[pos + 1] == b'<' => {
if bytes[pos + 2] == b'<' {
if let Some((target, off)) = radio_target::parse(&src[pos..]) {
brk!(Object::RadioTarget { target }, off, pos);
}
} else if bytes[pos + 2] != b'\n' {
if let Some((target, off)) = target::parse(&src[pos..]) {
brk!(Object::Target { target }, off, pos);
}
}
}
b'[' => {
if bytes[pos + 1..].starts_with(b"fn:") {
if let Some((label, def, off)) = fn_ref::parse(&src[pos..]) {
brk!(Object::FnRef { label, def }, off, pos);
}
}
if bytes[pos + 1] == b'[' {
if let Some((path, desc, off)) = link::parse(&src[pos..]) {
brk!(Object::Link { path, desc }, off, pos);
}
}
if let Some((cookie, off)) = cookie::parse(&src[pos..]) {
brk!(Object::Cookie(cookie), off, pos);
}
// TODO: Timestamp
}
b'{' | b' ' | b'"' | b',' | b'(' | b'\n' => pre += 1,
_ => (),
} }
b'<' if bytes[pos + 1] == b'<' => {
match bytes[pre] { if bytes[pos + 2] == b'<' {
b'*' => { if let Some((target, off)) = radio_target::parse(&src[pos..]) {
if let Some(end) = emphasis::parse(&src[pre..], b'*') { brk!(Object::RadioTarget { target }, off, pos);
brk!(Object::Bold { end }, 1, pre); }
} else if bytes[pos + 2] != b'\n' {
if let Some((target, off)) = target::parse(&src[pos..]) {
brk!(Object::Target { target }, off, pos);
} }
} }
b'+' => {
if let Some(end) = emphasis::parse(&src[pre..], b'+') {
brk!(Object::Strike { end }, 1, pre);
}
}
b'/' => {
if let Some(end) = emphasis::parse(&src[pre..], b'/') {
brk!(Object::Italic { end }, 1, pre);
}
}
b'_' => {
if let Some(end) = emphasis::parse(&src[pre..], b'_') {
brk!(Object::Underline { end }, 1, pre);
}
}
b'=' => {
if let Some(end) = emphasis::parse(&src[pre..], b'=') {
brk!(Object::Verbatim(&src[pre + 1..pre + end]), end + 1, pre);
}
}
b'~' => {
if let Some(end) = emphasis::parse(&src[pre..], b'~') {
brk!(Object::Code(&src[pre + 1..pre + end]), end + 1, pre);
}
}
b'c' if src[pre..].starts_with("call_") => {
if let Some((name, args, inside_header, end_header, off)) =
inline_call::parse(&src[pre..])
{
brk!(
Object::InlineCall {
name,
args,
inside_header,
end_header,
},
off,
pre
);
}
}
b's' if src[pre..].starts_with("src_") => {
if let Some((lang, option, body, off)) = inline_src::parse(&src[pre..]) {
brk!(Object::InlineSrc { lang, option, body }, off, pre);
}
}
_ => (),
} }
b'[' => {
if bytes[pos + 1..].starts_with(b"fn:") {
if let Some((label, def, off)) = fn_ref::parse(&src[pos..]) {
brk!(Object::FnRef { label, def }, off, pos);
}
}
if let Some(off) = bs if bytes[pos + 1] == b'[' {
.find(&bytes[pos + 1..]) if let Some((path, desc, off)) = link::parse(&src[pos..]) {
.map(|i| i + pos + 1) brk!(Object::Link { path, desc }, off, pos);
.filter(|&i| i < src.len() - 2) }
{ }
pos = off;
} else { if let Some((cookie, off)) = cookie::parse(&src[pos..]) {
break (Object::Text(src), src.len(), None); brk!(Object::Cookie(cookie), off, pos);
}
// TODO: Timestamp
} }
b'{' | b' ' | b'"' | b',' | b'(' | b'\n' => {
if let Some((obj, off)) = parse_text_markup(&src[pos + 1..]) {
brk!(obj, off, pos + 1);
}
}
_ => {
if let Some((obj, off)) = parse_text_markup(&src[pos..]) {
brk!(obj, off, pos);
}
}
}
if let Some(off) = bs
.find(&bytes[pos + 1..])
.map(|i| i + pos + 1)
.filter(|&i| i < src.len() - 2)
{
pos = off;
} else {
break (Object::Text(src), src.len(), None);
} }
} }
} }
#[test] fn parse_text_markup<'a>(src: &'a str) -> Option<(Object<'a>, usize)> {
fn next_2() { match src.as_bytes()[0] {
// TODO: more tests b'*' => emphasis::parse(src, b'*').map(|end| (Object::Bold { end }, 1)),
assert_eq!(Object::next_2("*bold*"), (Object::Bold { end: 5 }, 1, None)); b'+' => emphasis::parse(src, b'+').map(|end| (Object::Strike { end }, 1)),
assert_eq!( b'/' => emphasis::parse(src, b'/').map(|end| (Object::Italic { end }, 1)),
Object::next_2("Normal =verbatim="), b'_' => emphasis::parse(src, b'_').map(|end| (Object::Underline { end }, 1)),
( b'=' => emphasis::parse(src, b'=').map(|end| (Object::Verbatim(&src[1..end]), end + 1)),
Object::Text("Normal "), b'~' => emphasis::parse(src, b'~').map(|end| (Object::Code(&src[1..end]), end + 1)),
"Normal ".len(), b's' if src.starts_with("src_") => inline_src::parse(src)
Some((Object::Verbatim("verbatim"), "=verbatim=".len())) .map(|(lang, option, body, off)| (Object::InlineSrc { lang, option, body }, off)),
) b'c' if src.starts_with("call_") => {
); inline_call::parse(src).map(|(name, args, inside_header, end_header, off)| {
(
Object::InlineCall {
name,
args,
inside_header,
end_header,
},
off,
)
})
}
_ => None,
}
}
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::*;
assert_eq!(parse("*bold*"), (Object::Bold { end: 5 }, 1, None));
assert_eq!(
parse("Normal =verbatim="),
(
Object::Text("Normal "),
"Normal ".len(),
Some((Object::Verbatim("verbatim"), "=verbatim=".len()))
)
);
// TODO: more tests
}
} }

View file

@ -1,8 +1,8 @@
//! Parser //! Parser
use crate::elements::*; use crate::elements::{self, *};
use crate::headline::*; use crate::headline::*;
use crate::objects::*; use crate::objects::{self, *};
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -227,7 +227,7 @@ impl<'a> Parser<'a> {
fn next_sec_or_hdl(&mut self) -> Event<'a> { fn next_sec_or_hdl(&mut self) -> Event<'a> {
let end = Headline::find_level(&self.text[self.off..], std::usize::MAX); let end = Headline::find_level(&self.text[self.off..], std::usize::MAX);
debug_assert!(end <= self.text.len()); debug_assert!(end <= self.text[self.off..].len());
if end != 0 { if end != 0 {
self.stack.push(Container::Section { self.stack.push(Container::Section {
end: self.off + end, end: self.off + end,
@ -241,7 +241,7 @@ impl<'a> Parser<'a> {
fn next_hdl(&mut self) -> Event<'a> { fn next_hdl(&mut self) -> Event<'a> {
let tail = &self.text[self.off..]; let tail = &self.text[self.off..];
let (hdl, off, end) = Headline::parse(tail); let (hdl, off, end) = Headline::parse(tail);
debug_assert!(end <= self.text.len()); debug_assert!(end <= self.text[self.off..].len());
self.stack.push(Container::Headline { self.stack.push(Container::Headline {
beg: self.off + off, beg: self.off + off,
end: self.off + end, end: self.off + end,
@ -257,7 +257,7 @@ impl<'a> Parser<'a> {
.take() .take()
.map(|(ele, off)| (Some(ele), off)) .map(|(ele, off)| (Some(ele), off))
.unwrap_or_else(|| { .unwrap_or_else(|| {
let (ele, off, next_ele) = Element::next_2(text); let (ele, off, next_ele) = elements::parse(text);
self.ele_buf = next_ele; self.ele_buf = next_ele;
(ele, off) (ele, off)
}); });
@ -344,49 +344,48 @@ impl<'a> Parser<'a> {
fn next_obj(&mut self, end: usize) -> Event<'a> { fn next_obj(&mut self, end: usize) -> Event<'a> {
let text = &self.text[self.off..end]; let text = &self.text[self.off..end];
let (obj, off) = self.obj_buf.take().unwrap_or_else(|| { let (obj, off) = self.obj_buf.take().unwrap_or_else(|| {
let (obj, off, next_obj) = Object::next_2(text); let (obj, off, next_obj) = objects::parse(text);
self.obj_buf = next_obj; self.obj_buf = next_obj;
(obj, off) (obj, off)
}); });
debug_assert!(off <= text.len()); debug_assert!(off <= text.len());
self.off += off;
match obj { match obj {
Object::Underline { end } => { Object::Underline { end } => {
debug_assert!(end <= text.len()); debug_assert!(end <= text.len());
self.stack.push(Container::Underline { self.stack.push(Container::Underline {
cont_end: self.off + end, cont_end: self.off + end - 1,
end: self.off + end + 1, end: self.off + end,
}); });
Event::UnderlineBeg
} }
Object::Strike { end } => { Object::Strike { end } => {
debug_assert!(end <= text.len()); debug_assert!(end <= text.len());
self.stack.push(Container::Strike { self.stack.push(Container::Strike {
cont_end: self.off + end, cont_end: self.off + end - 1,
end: self.off + end + 1, end: self.off + end,
}); });
Event::StrikeBeg
} }
Object::Italic { end } => { Object::Italic { end } => {
debug_assert!(end <= text.len()); debug_assert!(end <= text.len());
self.stack.push(Container::Italic { self.stack.push(Container::Italic {
cont_end: self.off + end, cont_end: self.off + end - 1,
end: self.off + end + 1, end: self.off + end,
}); });
Event::ItalicBeg
} }
Object::Bold { end } => { Object::Bold { end } => {
debug_assert!(end <= text.len()); debug_assert!(end <= text.len());
self.stack.push(Container::Bold { self.stack.push(Container::Bold {
cont_end: self.off + end, cont_end: self.off + end - 1,
end: self.off + end + 1, end: self.off + end,
}); });
Event::BoldBeg
} }
_ => (),
}
self.off += off;
match obj {
Object::Bold { .. } => Event::BoldBeg,
Object::Code(c) => Event::Code(c), Object::Code(c) => Event::Code(c),
Object::Cookie(c) => Event::Cookie(c), Object::Cookie(c) => Event::Cookie(c),
Object::FnRef { label, def } => Event::FnRef { label, def }, Object::FnRef { label, def } => Event::FnRef { label, def },
@ -402,15 +401,12 @@ impl<'a> Parser<'a> {
end_header, end_header,
}, },
Object::InlineSrc { lang, option, body } => Event::InlineSrc { lang, option, body }, Object::InlineSrc { lang, option, body } => Event::InlineSrc { lang, option, body },
Object::Italic { .. } => Event::ItalicBeg,
Object::Link { path, desc } => Event::Link { path, desc }, Object::Link { path, desc } => Event::Link { path, desc },
Object::Macros { name, args } => Event::Macros { name, args }, Object::Macros { name, args } => Event::Macros { name, args },
Object::RadioTarget { target } => Event::RadioTarget { target }, Object::RadioTarget { target } => Event::RadioTarget { target },
Object::Snippet { name, value } => Event::Snippet { name, value }, Object::Snippet { name, value } => Event::Snippet { name, value },
Object::Strike { .. } => Event::StrikeBeg,
Object::Target { target } => Event::Target { target }, Object::Target { target } => Event::Target { target },
Object::Text(t) => Event::Text(t), Object::Text(t) => Event::Text(t),
Object::Underline { .. } => Event::UnderlineBeg,
Object::Verbatim(v) => Event::Verbatim(v), Object::Verbatim(v) => Event::Verbatim(v),
} }
} }

View file

@ -10,18 +10,6 @@ macro_rules! expect {
}; };
} }
#[macro_export]
macro_rules! eol {
($src:expr) => {
memchr::memchr(b'\n', $src.as_bytes()).unwrap_or_else(|| $src.len())
};
($src:expr, $from:expr) => {
memchr::memchr(b'\n', $src.as_bytes()[$from..])
.map(|i| i + $from)
.unwrap_or_else(|| $src.len())
};
}
#[macro_export] #[macro_export]
macro_rules! skip_space { macro_rules! skip_space {
($src:ident) => { ($src:ident) => {
@ -39,17 +27,3 @@ macro_rules! skip_space {
.unwrap_or(0) .unwrap_or(0)
}; };
} }
#[macro_export]
macro_rules! skip_empty_line {
($src:ident, $from:expr) => {{
let mut pos = $from;
loop {
if pos >= $src.len() || $src.as_bytes()[pos] != b'\n' {
break pos;
} else {
pos += 1;
}
}
}};
}