refactor: cleanup parse function

This commit is contained in:
PoiScript 2019-02-08 21:34:58 +08:00
parent c1154a1853
commit c5b14256f0
25 changed files with 1299 additions and 1234 deletions

View file

@ -1,61 +1,61 @@
use crate::lines::Lines;
use memchr::memchr2;
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct Block;
// return (name, args, contents-begin, contents-end, end)
#[inline]
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
debug_assert!(src.starts_with("#+"));
impl Block {
// return (name, args, contents-begin, contents-end, end)
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
debug_assert!(src.starts_with("#+"));
if !src[2..8].eq_ignore_ascii_case("BEGIN_") {
return None;
}
let name = memchr2(b' ', b'\n', src.as_bytes())
.filter(|&i| src.as_bytes()[8..i].iter().all(|c| c.is_ascii_alphabetic()))?;
let mut lines = Lines::new(src);
let (pre_cont_end, cont_beg, _) = lines.next()?;
let args = if pre_cont_end == name {
None
} else {
Some(&src[name..pre_cont_end])
};
let name = &src[8..name];
let end_line = format!(r"#+END_{}", name);
let mut pre_end = cont_beg;
for (_, end, line) in lines {
if line.trim().eq_ignore_ascii_case(&end_line) {
return Some((name, args, cont_beg, pre_end, end));
} else {
pre_end = end;
}
}
None
if src[2..8].to_uppercase() != "BEGIN_" {
return None;
}
let name = memchr2(b' ', b'\n', src.as_bytes())
.filter(|&i| src.as_bytes()[8..i].iter().all(|c| c.is_ascii_alphabetic()))?;
let mut lines = Lines::new(src);
let (pre_cont_end, cont_beg, _) = lines.next()?;
let args = if pre_cont_end == name {
None
} else {
Some(&src[name..pre_cont_end])
};
let name = &src[8..name];
let end_line = format!(r"#+END_{}", name.to_uppercase());
let mut pre_end = cont_beg;
for (_, end, line) in lines {
if line.trim() == end_line {
return Some((name, args, cont_beg, pre_end, end));
} else {
pre_end = end;
}
}
None
}
#[test]
fn parse() {
assert_eq!(
Block::parse("#+BEGIN_SRC\n#+END_SRC"),
Some(("SRC", None, 12, 12, 21))
);
assert_eq!(
Block::parse(
r#"#+BEGIN_SRC rust
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
assert_eq!(
parse("#+BEGIN_SRC\n#+END_SRC"),
Some(("SRC", None, 12, 12, 21))
);
assert_eq!(
parse(
r#"#+BEGIN_SRC rust
fn main() {
// print "Hello World!" to the console
println!("Hello World!");
}
#+END_SRC
"#
),
Some(("SRC", Some(" rust"), 17, 104, 114))
);
// TODO: more testing
),
Some(("SRC", Some(" rust"), 17, 104, 114))
);
// TODO: more testing
}
}

View file

@ -1,62 +1,62 @@
use crate::lines::Lines;
use memchr::memchr2;
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct DynBlock;
// return (name, parameters, contents-begin, contents-end, end)
#[inline]
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
debug_assert!(src.starts_with("#+"));
impl DynBlock {
// return (name, parameters, contents-begin, contents-end, end)
pub fn parse(src: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
debug_assert!(src.starts_with("#+"));
if !src[2..9].eq_ignore_ascii_case("BEGIN: ") {
return None;
}
let bytes = src.as_bytes();
let args = eol!(src);
let name = memchr2(b' ', b'\n', &bytes[9..])
.map(|i| i + 9)
.filter(|&i| {
src.as_bytes()[9..i]
.iter()
.all(|&c| c.is_ascii_alphabetic())
})?;
let mut lines = Lines::new(src);
let (mut pre_cont_end, _, _) = lines.next()?;
for (cont_end, end, line) in lines {
if line.trim().eq_ignore_ascii_case("#+END:") {
return Some((
&src[8..name].trim(),
if name == args {
None
} else {
Some(&src[name..args].trim())
},
args,
pre_cont_end,
end,
));
}
pre_cont_end = cont_end;
}
None
if !src[2..9].eq_ignore_ascii_case("BEGIN: ") {
return None;
}
let bytes = src.as_bytes();
let args = eol!(src);
let name = memchr2(b' ', b'\n', &bytes[9..])
.map(|i| i + 9)
.filter(|&i| {
src.as_bytes()[9..i]
.iter()
.all(|&c| c.is_ascii_alphabetic())
})?;
let mut lines = Lines::new(src);
let (mut pre_cont_end, _, _) = lines.next()?;
for (cont_end, end, line) in lines {
if line.trim().eq_ignore_ascii_case("#+END:") {
return Some((
&src[8..name].trim(),
if name == args {
None
} else {
Some(&src[name..args].trim())
},
args,
pre_cont_end,
end,
));
}
pre_cont_end = cont_end;
}
None
}
#[test]
fn parse() {
// TODO: testing
assert_eq!(
DynBlock::parse(
r"#+BEGIN: clocktable :scope file
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
// TODO: testing
assert_eq!(
parse(
r"#+BEGIN: clocktable :scope file
CONTENTS
#+END:
"
),
Some(("clocktable", Some(":scope file"), 31, 40, 48))
)
),
Some(("clocktable", Some(":scope file"), 31, 40, 48))
)
}
}

View file

@ -1,57 +1,54 @@
use memchr::memchr;
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct FnDef;
#[inline]
pub fn parse(src: &str) -> Option<(&str, &str, usize)> {
debug_assert!(src.starts_with("[fn:"));
impl FnDef {
pub fn parse(src: &str) -> Option<(&str, &str, usize)> {
debug_assert!(src.starts_with("[fn:"));
let label = memchr(b']', src.as_bytes()).filter(|&i| {
i != 4
&& src.as_bytes()[4..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})?;
let label = memchr(b']', src.as_bytes()).filter(|&i| {
i != 4
&& src.as_bytes()[4..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})?;
let end = eol!(src);
let end = eol!(src);
Some((&src[4..label], &src[label + 1..end], end))
}
Some((&src[4..label], &src[label + 1..end], end))
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
assert_eq!(
parse("[fn:1] https://orgmode.org").unwrap(),
(
"1",
" https://orgmode.org",
"[fn:1] https://orgmode.org".len()
)
);
assert_eq!(
parse("[fn:word_1] https://orgmode.org").unwrap(),
(
"word_1",
" https://orgmode.org",
"[fn:word_1] https://orgmode.org".len()
)
);
assert_eq!(
parse("[fn:WORD-1] https://orgmode.org").unwrap(),
(
"WORD-1",
" https://orgmode.org",
"[fn:WORD-1] https://orgmode.org".len()
)
);
assert_eq!(parse("[fn:WORD]").unwrap(), ("WORD", "", "[fn:WORD]".len()));
assert!(parse("[fn:] https://orgmode.org").is_none());
assert!(parse("[fn:wor d] https://orgmode.org").is_none());
assert!(parse("[fn:WORD https://orgmode.org").is_none());
}
}
#[test]
fn parse() {
assert_eq!(
FnDef::parse("[fn:1] https://orgmode.org").unwrap(),
(
"1",
" https://orgmode.org",
"[fn:1] https://orgmode.org".len()
)
);
assert_eq!(
FnDef::parse("[fn:word_1] https://orgmode.org").unwrap(),
(
"word_1",
" https://orgmode.org",
"[fn:word_1] https://orgmode.org".len()
)
);
assert_eq!(
FnDef::parse("[fn:WORD-1] https://orgmode.org").unwrap(),
(
"WORD-1",
" https://orgmode.org",
"[fn:WORD-1] https://orgmode.org".len()
)
);
assert_eq!(
FnDef::parse("[fn:WORD]").unwrap(),
("WORD", "", "[fn:WORD]".len())
);
assert!(FnDef::parse("[fn:] https://orgmode.org").is_none());
assert!(FnDef::parse("[fn:wor d] https://orgmode.org").is_none());
assert!(FnDef::parse("[fn:WORD https://orgmode.org").is_none());
}

View file

@ -1,7 +1,5 @@
use memchr::{memchr, memchr2};
pub struct Keyword;
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub enum Key<'a> {
@ -24,114 +22,116 @@ pub enum Key<'a> {
Call,
}
impl Keyword {
// return (key, value, offset)
pub fn parse(src: &str) -> Option<(Key<'_>, &str, usize)> {
debug_assert!(src.starts_with("#+"));
pub fn parse(src: &str) -> Option<(Key<'_>, &str, usize)> {
debug_assert!(src.starts_with("#+"));
let bytes = src.as_bytes();
let key_end = memchr2(b':', b'[', bytes).filter(|&i| {
bytes[2..i]
.iter()
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
})?;
let bytes = src.as_bytes();
let key_end = memchr2(b':', b'[', bytes).filter(|&i| {
bytes[2..i]
.iter()
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
})?;
let option = if bytes[key_end] == b'[' {
let option =
memchr(b']', bytes).filter(|&i| bytes[key_end..i].iter().all(|&c| c != b'\n'))?;
expect!(src, option + 1, b':')?;
option + 1
} else {
key_end
};
let option = if bytes[key_end] == b'[' {
let option =
memchr(b']', bytes).filter(|&i| bytes[key_end..i].iter().all(|&c| c != b'\n'))?;
expect!(src, option + 1, b':')?;
option + 1
} else {
key_end
};
// includes the eol character
let end = memchr::memchr(b'\n', src.as_bytes())
.map(|i| i + 1)
.unwrap_or_else(|| src.len());
// includes the eol character
let end = memchr::memchr(b'\n', src.as_bytes())
.map(|i| i + 1)
.unwrap_or_else(|| src.len());
Some((
match &src[2..key_end] {
key if key.eq_ignore_ascii_case("CAPTION") => Key::Caption {
option: if key_end == option {
None
} else {
Some(&src[key_end + 1..option - 1])
},
Some((
match src[2..key_end].to_uppercase().as_str() {
"AUTHOR" => Key::Author,
"CALL" => Key::Call,
"DATE" => Key::Date,
"HEADER" => Key::Header,
"NAME" => Key::Name,
"PLOT" => Key::Plot,
"TITLE" => Key::Title,
"RESULTS" => Key::Results {
option: if key_end == option {
None
} else {
Some(&src[key_end + 1..option - 1])
},
key if key.eq_ignore_ascii_case("HEADER") => Key::Header,
key if key.eq_ignore_ascii_case("NAME") => Key::Name,
key if key.eq_ignore_ascii_case("PLOT") => Key::Plot,
key if key.eq_ignore_ascii_case("RESULTS") => Key::Results {
option: if key_end == option {
None
} else {
Some(&src[key_end + 1..option - 1])
},
},
key if key.eq_ignore_ascii_case("AUTHOR") => Key::Author,
key if key.eq_ignore_ascii_case("DATE") => Key::Date,
key if key.eq_ignore_ascii_case("TITLE") => Key::Title,
key if key.eq_ignore_ascii_case("CALL") => Key::Call,
key if key.starts_with("ATTR_") => Key::Attr {
backend: &src["#+ATTR_".len()..key_end],
},
key => Key::Custom(key),
},
&src[option + 1..end].trim(),
end,
))
"CAPTION" => Key::Caption {
option: if key_end == option {
None
} else {
Some(&src[key_end + 1..option - 1])
},
},
key if key.starts_with("ATTR_") => Key::Attr {
backend: &src["#+ATTR_".len()..key_end],
},
_ => Key::Custom(&src[2..key_end]),
},
&src[option + 1..end].trim(),
end,
))
}
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::*;
assert_eq!(
parse("#+KEY:"),
Some((Key::Custom("KEY"), "", "#+KEY:".len()))
);
assert_eq!(
parse("#+KEY: VALUE"),
Some((Key::Custom("KEY"), "VALUE", "#+KEY: VALUE".len()))
);
assert_eq!(
parse("#+K_E_Y: VALUE"),
Some((Key::Custom("K_E_Y"), "VALUE", "#+K_E_Y: VALUE".len()))
);
assert_eq!(
parse("#+KEY:VALUE\n"),
Some((Key::Custom("KEY"), "VALUE", "#+KEY:VALUE\n".len()))
);
assert!(parse("#+KE Y: VALUE").is_none());
assert!(parse("#+ KEY: VALUE").is_none());
assert_eq!(
parse("#+RESULTS:"),
Some((Key::Results { option: None }, "", "#+RESULTS:".len()))
);
assert_eq!(
parse("#+ATTR_LATEX: :width 5cm"),
Some((
Key::Attr { backend: "LATEX" },
":width 5cm",
"#+ATTR_LATEX: :width 5cm".len()
))
);
assert_eq!(
parse("#+CALL: double(n=4)"),
Some((Key::Call, "double(n=4)", "#+CALL: double(n=4)".len()))
);
assert_eq!(
parse("#+CAPTION[Short caption]: Longer caption."),
Some((
Key::Caption {
option: Some("Short caption")
},
"Longer caption.",
"#+CAPTION[Short caption]: Longer caption.".len()
))
);
}
}
#[test]
fn parse() {
assert_eq!(
Keyword::parse("#+KEY:"),
Some((Key::Custom("KEY"), "", "#+KEY:".len()))
);
assert_eq!(
Keyword::parse("#+KEY: VALUE"),
Some((Key::Custom("KEY"), "VALUE", "#+KEY: VALUE".len()))
);
assert_eq!(
Keyword::parse("#+K_E_Y: VALUE"),
Some((Key::Custom("K_E_Y"), "VALUE", "#+K_E_Y: VALUE".len()))
);
assert_eq!(
Keyword::parse("#+KEY:VALUE\n"),
Some((Key::Custom("KEY"), "VALUE", "#+KEY:VALUE\n".len()))
);
assert!(Keyword::parse("#+KE Y: VALUE").is_none());
assert!(Keyword::parse("#+ KEY: VALUE").is_none());
assert_eq!(
Keyword::parse("#+RESULTS:"),
Some((Key::Results { option: None }, "", "#+RESULTS:".len()))
);
assert_eq!(
Keyword::parse("#+ATTR_LATEX: :width 5cm"),
Some((
Key::Attr { backend: "LATEX" },
":width 5cm",
"#+ATTR_LATEX: :width 5cm".len()
))
);
assert_eq!(
Keyword::parse("#+CALL: double(n=4)"),
Some((Key::Call, "double(n=4)", "#+CALL: double(n=4)".len()))
);
assert_eq!(
Keyword::parse("#+CAPTION[Short caption]: Longer caption."),
Some((
Key::Caption {
option: Some("Short caption")
},
"Longer caption.",
"#+CAPTION[Short caption]: Longer caption.".len()
))
);
}

View file

@ -1,157 +1,159 @@
use crate::lines::Lines;
pub struct List;
impl List {
#[inline]
pub fn is_item(src: &str) -> (bool, bool) {
if src.is_empty() {
return (false, false);
}
let bytes = src.as_bytes();
let (i, ordered) = match bytes[0] {
b'*' | b'-' | b'+' => (1, false),
b'0'...b'9' => {
let i = bytes
.iter()
.position(|&c| !c.is_ascii_digit())
.unwrap_or_else(|| src.len() - 1);
let c = bytes[i];
if !(c == b'.' || c == b')') {
return (false, false);
}
(i + 1, true)
}
_ => return (false, false),
};
if i < src.len() {
// bullet is follwed by a space or line ending
(bytes[i] == b' ' || bytes[i] == b'\n', ordered)
} else {
(false, false)
}
#[inline]
pub fn is_item(src: &str) -> (bool, bool) {
if src.is_empty() {
return (false, false);
}
// returns (bullets, contents begin, contents end, end, has more)
pub fn parse(src: &str, ident: usize) -> (&str, usize, usize, usize, bool) {
debug_assert!(Self::is_item(&src[ident..]).0);
debug_assert!(
src[..ident].chars().all(|c| c == ' ' || c == '\t'),
"{:?} doesn't starts with indentation {}",
src,
ident
);
let mut lines = Lines::new(src);
let (mut pre_cont_end, mut pre_end, first_line) = lines.next().unwrap();
let beg = match memchr::memchr(b' ', &first_line.as_bytes()[ident..]) {
Some(i) => i + ident + 1,
None => {
let len = first_line.len();
return (
&first_line,
len,
len,
len,
Self::is_item(lines.next().unwrap().2).0,
);
let bytes = src.as_bytes();
let (i, ordered) = match bytes[0] {
b'*' | b'-' | b'+' => (1, false),
b'0'...b'9' => {
let i = bytes
.iter()
.position(|&c| !c.is_ascii_digit())
.unwrap_or_else(|| src.len() - 1);
let c = bytes[i];
if !(c == b'.' || c == b')') {
return (false, false);
}
};
let bullet = &src[0..beg];
(i + 1, true)
}
_ => return (false, false),
};
while let Some((mut cont_end, mut end, mut line)) = lines.next() {
// this line is emtpy
if line.is_empty() {
if let Some((next_cont_end, next_end, next_line)) = lines.next() {
// next line is emtpy, too
if next_line.is_empty() {
return (bullet, beg, pre_cont_end, next_end, false);
} else {
// move to next line
pre_end = end;
cont_end = next_cont_end;
end = next_end;
line = next_line;
}
if i < src.len() {
// bullet is follwed by a space or line ending
(bytes[i] == b' ' || bytes[i] == b'\n', ordered)
} else {
(false, false)
}
}
// returns (bullets, contents begin, contents end, end, has more)
#[inline]
pub fn parse(src: &str, ident: usize) -> (&str, usize, usize, usize, bool) {
debug_assert!(is_item(&src[ident..]).0);
debug_assert!(
src[..ident].chars().all(|c| c == ' ' || c == '\t'),
"{:?} doesn't starts with indentation {}",
src,
ident
);
let mut lines = Lines::new(src);
let (mut pre_cont_end, mut pre_end, first_line) = lines.next().unwrap();
let beg = match memchr::memchr(b' ', &first_line.as_bytes()[ident..]) {
Some(i) => i + ident + 1,
None => {
let len = first_line.len();
return (
&first_line,
len,
len,
len,
is_item(lines.next().unwrap().2).0,
);
}
};
let bullet = &src[0..beg];
while let Some((mut cont_end, mut end, mut line)) = lines.next() {
// this line is emtpy
if line.is_empty() {
if let Some((next_cont_end, next_end, next_line)) = lines.next() {
// next line is emtpy, too
if next_line.is_empty() {
return (bullet, beg, pre_cont_end, next_end, false);
} else {
return (bullet, beg, pre_cont_end, end, false);
// move to next line
pre_end = end;
cont_end = next_cont_end;
end = next_end;
line = next_line;
}
} else {
return (bullet, beg, pre_cont_end, end, false);
}
let line_ident = Self::ident(line);
if line_ident < ident {
return (bullet, beg, pre_cont_end, pre_end, false);
} else if line_ident == ident {
return (
bullet,
beg,
pre_cont_end,
pre_end,
Self::is_item(&line[ident..]).0,
);
}
pre_end = end;
pre_cont_end = cont_end;
}
(bullet, beg, src.len(), src.len(), false)
let line_ident = self::ident(line);
if line_ident < ident {
return (bullet, beg, pre_cont_end, pre_end, false);
} else if line_ident == ident {
return (
bullet,
beg,
pre_cont_end,
pre_end,
is_item(&line[ident..]).0,
);
}
pre_end = end;
pre_cont_end = cont_end;
}
fn ident(src: &str) -> usize {
src.as_bytes()
.iter()
.position(|&c| c != b' ' && c != b'\t')
.unwrap_or(0)
(bullet, beg, src.len(), src.len(), false)
}
#[inline]
fn ident(src: &str) -> usize {
src.as_bytes()
.iter()
.position(|&c| c != b' ' && c != b'\t')
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
#[test]
fn is_item() {
use super::is_item;
assert_eq!(is_item("+ item"), (true, false));
assert_eq!(is_item("- item"), (true, false));
assert_eq!(is_item("10. item"), (true, true));
assert_eq!(is_item("10) item"), (true, true));
assert_eq!(is_item("1. item"), (true, true));
assert_eq!(is_item("1) item"), (true, true));
assert_eq!(is_item("10. "), (true, true));
assert_eq!(is_item("10.\n"), (true, true));
assert_eq!(is_item("10."), (false, false));
assert_eq!(is_item("+"), (false, false));
assert_eq!(is_item("-item"), (false, false));
assert_eq!(is_item("+item"), (false, false));
}
#[test]
fn parse() {
use super::parse;
assert_eq!(parse("+ item1\n+ item2\n+ item3", 0), ("+ ", 2, 7, 8, true));
assert_eq!(
parse("* item1\n\n* item2\n* item3", 0),
("* ", 2, 7, 9, true)
);
assert_eq!(
parse("- item1\n\n\n- item2\n- item3", 0),
("- ", 2, 7, 10, false)
);
assert_eq!(
parse("1. item1\n\n\n\n2. item2\n3. item3", 0),
("1. ", 3, 8, 11, false)
);
assert_eq!(
parse(" + item1\n + item2\n+ item3", 2),
(" + ", 4, 21, 22, false)
);
assert_eq!(
parse(" + item1\n + item2\n + item3", 2),
(" + ", 4, 9, 10, true)
);
assert_eq!(parse("+\n", 0), ("+", 1, 1, 1, false));
assert_eq!(parse("+\n+ item2\n+ item3", 0), ("+", 1, 1, 1, true));
assert_eq!(parse("1) item1", 0), ("1) ", 3, 8, 8, false));
assert_eq!(parse("1) item1\n", 0), ("1) ", 3, 8, 9, false));
}
}
#[test]
fn is_item() {
assert_eq!(List::is_item("+ item"), (true, false));
assert_eq!(List::is_item("- item"), (true, false));
assert_eq!(List::is_item("10. item"), (true, true));
assert_eq!(List::is_item("10) item"), (true, true));
assert_eq!(List::is_item("1. item"), (true, true));
assert_eq!(List::is_item("1) item"), (true, true));
assert_eq!(List::is_item("10. "), (true, true));
assert_eq!(List::is_item("10.\n"), (true, true));
assert_eq!(List::is_item("10."), (false, false));
assert_eq!(List::is_item("+"), (false, false));
assert_eq!(List::is_item("-item"), (false, false));
assert_eq!(List::is_item("+item"), (false, false));
}
#[test]
fn parse() {
assert_eq!(
List::parse("+ item1\n+ item2\n+ item3", 0),
("+ ", 2, 7, 8, true)
);
assert_eq!(
List::parse("* item1\n\n* item2\n* item3", 0),
("* ", 2, 7, 9, true)
);
assert_eq!(
List::parse("- item1\n\n\n- item2\n- item3", 0),
("- ", 2, 7, 10, false)
);
assert_eq!(
List::parse("1. item1\n\n\n\n2. item2\n3. item3", 0),
("1. ", 3, 8, 11, false)
);
assert_eq!(
List::parse(" + item1\n + item2\n+ item3", 2),
(" + ", 4, 21, 22, false)
);
assert_eq!(
List::parse(" + item1\n + item2\n + item3", 2),
(" + ", 4, 9, 10, true)
);
assert_eq!(List::parse("+\n", 0), ("+", 1, 1, 1, false));
assert_eq!(List::parse("+\n+ item2\n+ item3", 0), ("+", 1, 1, 1, true));
assert_eq!(List::parse("1) item1", 0), ("1) ", 3, 8, 8, false));
assert_eq!(List::parse("1) item1\n", 0), ("1) ", 3, 8, 9, false));
}

View file

@ -5,12 +5,7 @@ pub mod keyword;
pub mod list;
pub mod rule;
pub use self::block::Block;
pub use self::dyn_block::DynBlock;
pub use self::fn_def::FnDef;
pub use self::keyword::{Key, Keyword};
pub use self::list::List;
pub use self::rule::Rule;
pub use self::keyword::Key;
use memchr::memchr;
use memchr::memchr_iter;
@ -118,9 +113,9 @@ impl<'a> Element<'a> {
};
}
// Unlike other element, footnote definition must starts at column 0
// Unlike other element, footnote def must starts at column 0
if bytes[pos..].starts_with(b"[fn:") {
if let Some((label, cont, off)) = FnDef::parse(&src[pos..]) {
if let Some((label, cont, off)) = fn_def::parse(&src[pos..]) {
brk!(Element::FnDef { label, cont }, off + 1);
}
}
@ -138,7 +133,7 @@ impl<'a> Element<'a> {
pos = skip_space!(src, pos);
let (is_item, ordered) = List::is_item(&src[pos..]);
let (is_item, ordered) = list::is_item(&src[pos..]);
if is_item {
let list = Element::List {
ident: pos - line_beg,
@ -163,7 +158,7 @@ impl<'a> Element<'a> {
// Rule
if bytes[pos] == b'-' {
let off = Rule::parse(&src[pos..]);
let off = rule::parse(&src[pos..]);
if off != 0 {
brk!(Element::Rule, off);
}
@ -178,7 +173,7 @@ impl<'a> Element<'a> {
}
if bytes[pos..].starts_with(b"#+") {
if let Some((name, args, cont_beg, cont_end, end)) = Block::parse(&src[pos..]) {
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),
@ -214,7 +209,7 @@ impl<'a> Element<'a> {
};
}
if let Some((name, args, cont_beg, cont_end, end)) = DynBlock::parse(&src[pos..]) {
if let Some((name, args, cont_beg, cont_end, end)) = dyn_block::parse(&src[pos..]) {
brk!(
Element::DynBlock {
name,
@ -226,7 +221,7 @@ impl<'a> Element<'a> {
)
}
if let Some((key, value, off)) = Keyword::parse(&src[pos..]) {
if let Some((key, value, off)) = keyword::parse(&src[pos..]) {
brk!(
if let Key::Call = key {
Element::Call { value }

View file

@ -1,37 +1,37 @@
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct Rule;
impl Rule {
pub fn parse(src: &str) -> usize {
let end = memchr::memchr(b'\n', src.as_bytes())
.map(|i| i + 1)
.unwrap_or_else(|| src.len());
let rules = &src[0..end].trim();
if rules.len() >= 5 && rules.chars().all(|c| c == '-') {
end
} else {
0
}
#[inline]
pub fn parse(src: &str) -> usize {
let end = memchr::memchr(b'\n', src.as_bytes())
.map(|i| i + 1)
.unwrap_or_else(|| src.len());
let rules = &src[0..end].trim();
if rules.len() >= 5 && rules.chars().all(|c| c == '-') {
end
} else {
0
}
}
#[test]
fn parse() {
assert_eq!(Rule::parse("-----"), "-----".len());
assert_eq!(Rule::parse("--------"), "--------".len());
assert_eq!(Rule::parse(" -----"), " -----".len());
assert_eq!(Rule::parse("\t\t-----"), "\t\t-----".len());
assert_eq!(Rule::parse("\t\t-----\n"), "\t\t-----\n".len());
assert_eq!(Rule::parse("\t\t----- \n"), "\t\t----- \n".len());
assert_eq!(Rule::parse(""), 0);
assert_eq!(Rule::parse("----"), 0);
assert_eq!(Rule::parse(" ----"), 0);
assert_eq!(Rule::parse(" 0----"), 0);
assert_eq!(Rule::parse("0 ----"), 0);
assert_eq!(Rule::parse("0------"), 0);
assert_eq!(Rule::parse("----0----"), 0);
assert_eq!(Rule::parse("\t\t----"), 0);
assert_eq!(Rule::parse("------0"), 0);
assert_eq!(Rule::parse("----- 0"), 0);
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
assert_eq!(parse("-----"), "-----".len());
assert_eq!(parse("--------"), "--------".len());
assert_eq!(parse(" -----"), " -----".len());
assert_eq!(parse("\t\t-----"), "\t\t-----".len());
assert_eq!(parse("\t\t-----\n"), "\t\t-----\n".len());
assert_eq!(parse("\t\t----- \n"), "\t\t----- \n".len());
assert_eq!(parse(""), 0);
assert_eq!(parse("----"), 0);
assert_eq!(parse(" ----"), 0);
assert_eq!(parse(" 0----"), 0);
assert_eq!(parse("0 ----"), 0);
assert_eq!(parse("0------"), 0);
assert_eq!(parse("----0----"), 0);
assert_eq!(parse("\t\t----"), 0);
assert_eq!(parse("------0"), 0);
assert_eq!(parse("----- 0"), 0);
}
}