feat(parser): improve list parsing
This commit is contained in:
parent
ecf0d7e67d
commit
c4041aefb6
6 changed files with 208 additions and 210 deletions
|
|
@ -25,20 +25,23 @@ pub enum Clock<'a> {
|
|||
|
||||
impl<'a> Clock<'a> {
|
||||
pub(crate) fn parse(text: &'a str) -> Option<(Clock<'a>, usize)> {
|
||||
let (text, off) = memchr(b'\n', text.as_bytes())
|
||||
let (text, eol) = memchr(b'\n', text.as_bytes())
|
||||
.map(|i| (text[..i].trim(), i + 1))
|
||||
.unwrap_or_else(|| (text.trim(), text.len()));
|
||||
|
||||
let tail = memchr(b' ', text.as_bytes())
|
||||
.filter(|&i| &text[0..i] == "CLOCK:")
|
||||
.map(|i| text[i..].trim_start())?;
|
||||
if !text.starts_with("CLOCK:") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let tail = &text["CLOCK:".len()..].trim_start();
|
||||
|
||||
if !tail.starts_with('[') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (timestamp, tail) =
|
||||
Timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start()))?;
|
||||
let (timestamp, off) = Timestamp::parse_inactive(tail)?;
|
||||
|
||||
let tail = tail[off..].trim();
|
||||
|
||||
match timestamp {
|
||||
Timestamp::InactiveRange {
|
||||
|
|
@ -62,7 +65,7 @@ impl<'a> Clock<'a> {
|
|||
delay,
|
||||
duration,
|
||||
},
|
||||
off,
|
||||
eol,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
|
|
@ -72,20 +75,14 @@ impl<'a> Clock<'a> {
|
|||
start,
|
||||
repeater,
|
||||
delay,
|
||||
} => {
|
||||
if tail.as_bytes().iter().all(u8::is_ascii_whitespace) {
|
||||
Some((
|
||||
Clock::Running {
|
||||
start,
|
||||
repeater,
|
||||
delay,
|
||||
},
|
||||
off,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} if tail.is_empty() => Some((
|
||||
Clock::Running {
|
||||
start,
|
||||
repeater,
|
||||
delay,
|
||||
},
|
||||
eol,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,22 +2,24 @@ use memchr::memchr;
|
|||
|
||||
#[inline]
|
||||
pub fn parse(text: &str) -> Option<(&str, &str, usize)> {
|
||||
debug_assert!(text.starts_with("[fn:"));
|
||||
if text.starts_with("[fn:") {
|
||||
let (label, off) = memchr(b']', text.as_bytes())
|
||||
.filter(|&i| {
|
||||
i != 4
|
||||
&& text.as_bytes()["[fn:".len()..i]
|
||||
.iter()
|
||||
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
||||
})
|
||||
.map(|i| (&text["[fn:".len()..i], i + 1))?;
|
||||
|
||||
let (label, off) = memchr(b']', text.as_bytes())
|
||||
.filter(|&i| {
|
||||
i != 4
|
||||
&& text.as_bytes()["[fn:".len()..i]
|
||||
.iter()
|
||||
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
||||
})
|
||||
.map(|i| (&text["[fn:".len()..i], i + 1))?;
|
||||
let (content, off) = memchr(b'\n', text.as_bytes())
|
||||
.map(|i| (&text[off..i], i))
|
||||
.unwrap_or_else(|| (&text[off..], text.len()));
|
||||
|
||||
let (content, off) = memchr(b'\n', text.as_bytes())
|
||||
.map(|i| (&text[off..i], i))
|
||||
.unwrap_or_else(|| (&text[off..], text.len()));
|
||||
|
||||
Some((label, content, off))
|
||||
Some((label, content, off))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,16 +1,63 @@
|
|||
use memchr::memchr_iter;
|
||||
use std::iter::once;
|
||||
|
||||
// (indentation, ordered, limit, end)
|
||||
#[inline]
|
||||
pub fn is_item(text: &str) -> Option<(bool, &str)> {
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
pub fn parse(text: &str) -> Option<(usize, bool, usize, usize)> {
|
||||
let (indent, tail) = text
|
||||
.find(|c| c != ' ')
|
||||
.map(|off| (off, &text[off..]))
|
||||
.unwrap_or((0, text));
|
||||
|
||||
let ordered = is_item(tail)?;
|
||||
let bytes = text.as_bytes();
|
||||
let mut lines = memchr_iter(b'\n', bytes)
|
||||
.map(|i| i + 1)
|
||||
.chain(once(text.len()));
|
||||
let mut pos = lines.next()?;
|
||||
|
||||
while let Some(i) = lines.next() {
|
||||
let line = &text[pos..i];
|
||||
return if let Some(line_indent) = line.find(|c: char| !c.is_whitespace()) {
|
||||
// this line is no empty
|
||||
if line_indent < indent
|
||||
|| (line_indent == indent && is_item(&line[line_indent..]).is_none())
|
||||
{
|
||||
Some((indent, ordered, pos, pos))
|
||||
} else {
|
||||
pos = i;
|
||||
continue;
|
||||
}
|
||||
} else if let Some(next_i) = lines.next() {
|
||||
// this line is empty
|
||||
let line = &text[i..next_i];
|
||||
if let Some(line_indent) = line.find(|c: char| !c.is_whitespace()) {
|
||||
if line_indent < indent
|
||||
|| (line_indent == indent && is_item(&line[line_indent..]).is_none())
|
||||
{
|
||||
Some((indent, ordered, pos, pos))
|
||||
} else {
|
||||
pos = next_i;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
Some((indent, ordered, pos, next_i))
|
||||
}
|
||||
} else {
|
||||
Some((indent, ordered, pos, i))
|
||||
};
|
||||
}
|
||||
|
||||
Some((indent, ordered, pos, pos))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_item(text: &str) -> Option<bool> {
|
||||
let bytes = text.as_bytes();
|
||||
match bytes[0] {
|
||||
match bytes.get(0)? {
|
||||
b'*' | b'-' | b'+' => {
|
||||
if text.len() > 1 && (bytes[1] == b' ' || bytes[1] == b'\n') {
|
||||
Some((false, &text[0..2]))
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -21,10 +68,10 @@ pub fn is_item(text: &str) -> Option<(bool, &str)> {
|
|||
.position(|&c| !c.is_ascii_digit())
|
||||
.unwrap_or_else(|| text.len() - 1);
|
||||
if (bytes[i] == b'.' || bytes[i] == b')')
|
||||
&& i + 1 < text.len()
|
||||
&& text.len() > i + 1
|
||||
&& (bytes[i + 1] == b' ' || bytes[i + 1] == b'\n')
|
||||
{
|
||||
Some((true, &text[0..i + 2]))
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -33,141 +80,79 @@ pub fn is_item(text: &str) -> Option<(bool, &str)> {
|
|||
}
|
||||
}
|
||||
|
||||
// check if list item ends at this line
|
||||
#[inline]
|
||||
fn is_item_ends(line: &str, ident: usize) -> Option<&str> {
|
||||
debug_assert!(!line.is_empty());
|
||||
|
||||
let line_ident = line
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.position(|&c| c != b' ' && c != b'\t')
|
||||
.unwrap_or(0);
|
||||
|
||||
debug_assert!(line_ident >= ident, "{} >= {}", line_ident, ident);
|
||||
|
||||
if line_ident == ident {
|
||||
is_item(&line[ident..]).map(|(_, bullet)| bullet)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
#[test]
|
||||
fn test_is_item() {
|
||||
assert_eq!(is_item("+ item"), Some(false));
|
||||
assert_eq!(is_item("- item"), Some(false));
|
||||
assert_eq!(is_item("10. item"), Some(true));
|
||||
assert_eq!(is_item("10) item"), Some(true));
|
||||
assert_eq!(is_item("1. item"), Some(true));
|
||||
assert_eq!(is_item("1) item"), Some(true));
|
||||
assert_eq!(is_item("10. "), Some(true));
|
||||
assert_eq!(is_item("10.\n"), Some(true));
|
||||
assert_eq!(is_item("10."), None);
|
||||
assert_eq!(is_item("+"), None);
|
||||
assert_eq!(is_item("-item"), None);
|
||||
assert_eq!(is_item("+item"), None);
|
||||
}
|
||||
|
||||
// return (limit, end, next item bullet)
|
||||
#[inline]
|
||||
pub fn parse(text: &str, ident: usize) -> (usize, usize, Option<&str>) {
|
||||
let bytes = text.as_bytes();
|
||||
let mut lines = memchr_iter(b'\n', bytes);
|
||||
let mut pos = if let Some(i) = lines.next() {
|
||||
i + 1
|
||||
} else {
|
||||
return (text.len(), text.len(), None);
|
||||
};
|
||||
|
||||
while let Some(i) = lines.next() {
|
||||
return if bytes[pos..i].iter().all(u8::is_ascii_whitespace) {
|
||||
if let Some(nexti) = lines.next() {
|
||||
if bytes[i + 1..nexti].iter().all(u8::is_ascii_whitespace) {
|
||||
// two consecutive empty lines
|
||||
(pos - 1, nexti + 1, None)
|
||||
} else if let Some(next) = is_item_ends(&text[i + 1..nexti], ident) {
|
||||
(pos - 1, i + 1, Some(next))
|
||||
} else {
|
||||
pos = nexti + 1;
|
||||
continue;
|
||||
}
|
||||
} else if bytes[i + 1..].iter().all(u8::is_ascii_whitespace) {
|
||||
// two consecutive empty lines
|
||||
(pos - 1, text.len(), None)
|
||||
} else if let Some(next) = is_item_ends(&text[i + 1..], ident) {
|
||||
(pos - 1, i + 1, Some(next))
|
||||
} else {
|
||||
(text.len(), text.len(), None)
|
||||
}
|
||||
} else if let Some(next) = is_item_ends(&text[pos..i], ident) {
|
||||
(pos - 1, pos, Some(next))
|
||||
} else {
|
||||
pos = i + 1;
|
||||
continue;
|
||||
};
|
||||
}
|
||||
|
||||
if bytes[pos..].iter().all(u8::is_ascii_whitespace) {
|
||||
(pos - 1, text.len(), None)
|
||||
} else if let Some(next) = is_item_ends(&text[pos..], ident) {
|
||||
(pos - 1, pos, Some(next))
|
||||
} else {
|
||||
(text.len(), text.len(), None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn is_item() {
|
||||
use super::is_item;
|
||||
|
||||
assert_eq!(is_item("+ item"), Some((false, "+ ")));
|
||||
assert_eq!(is_item("- item"), Some((false, "- ")));
|
||||
assert_eq!(is_item("10. item"), Some((true, "10. ")));
|
||||
assert_eq!(is_item("10) item"), Some((true, "10) ")));
|
||||
assert_eq!(is_item("1. item"), Some((true, "1. ")));
|
||||
assert_eq!(is_item("1) item"), Some((true, "1) ")));
|
||||
assert_eq!(is_item("10. "), Some((true, "10. ")));
|
||||
assert_eq!(is_item("10.\n"), Some((true, "10.\n")));
|
||||
assert_eq!(is_item("10."), None);
|
||||
assert_eq!(is_item("+"), None);
|
||||
assert_eq!(is_item("-item"), None);
|
||||
assert_eq!(is_item("+item"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
assert_eq!(
|
||||
parse("item1\n+ item2", 0),
|
||||
("item1".len(), "item1\n".len(), Some("+ "))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("item1\n \n* item2", 0),
|
||||
("item1".len(), "item1\n \n".len(), Some("* "))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("item1\n \n \n* item2", 0),
|
||||
("item1".len(), "item1\n \n \n".len(), None)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("item1\n \n ", 0),
|
||||
("item1".len(), "item1\n \n ".len(), None)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("item1\n + item2\n ", 0),
|
||||
(
|
||||
"item1\n + item2".len(),
|
||||
"item1\n + item2\n ".len(),
|
||||
None
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("item1\n \n + item2\n \n+ item 3", 0),
|
||||
(
|
||||
"item1\n \n + item2".len(),
|
||||
"item1\n \n + item2\n \n".len(),
|
||||
Some("+ ")
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("item1\n \n + item2", 2),
|
||||
("item1".len(), "item1\n \n".len(), Some("+ "))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("1\n\n - 2\n\n - 3\n\n+ 4", 0),
|
||||
(
|
||||
"1\n\n - 2\n\n - 3".len(),
|
||||
"1\n\n - 2\n\n - 3\n\n".len(),
|
||||
Some("+ ")
|
||||
)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
assert_eq!(
|
||||
parse("+ item1\n+ item2"),
|
||||
Some((0, false, "+ item1\n+ item2".len(), "+ item1\n+ item2".len()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("* item1\n \n* item2"),
|
||||
Some((
|
||||
0,
|
||||
false,
|
||||
"* item1\n \n* item2".len(),
|
||||
"* item1\n \n* item2".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("* item1\n \n \n* item2"),
|
||||
Some((0, false, "* item1\n".len(), "* item1\n \n \n".len()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("* item1\n \n "),
|
||||
Some((0, false, "+ item1\n".len(), "* item1\n \n ".len()))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("+ item1\n + item2\n "),
|
||||
Some((
|
||||
0,
|
||||
false,
|
||||
"+ item1\n + item2\n".len(),
|
||||
"+ item1\n + item2\n ".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("+ item1\n \n + item2\n \n+ item 3"),
|
||||
Some((
|
||||
0,
|
||||
false,
|
||||
"+ item1\n \n + item2\n \n+ item 3".len(),
|
||||
"+ item1\n \n + item2\n \n+ item 3".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse(" + item1\n \n + item2"),
|
||||
Some((
|
||||
2,
|
||||
false,
|
||||
" + item1\n \n + item2".len(),
|
||||
" + item1\n \n + item2".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("+ 1\n\n - 2\n\n - 3\n\n+ 4"),
|
||||
Some((
|
||||
0,
|
||||
false,
|
||||
"+ 1\n\n - 2\n\n - 3\n\n+ 4".len(),
|
||||
"+ 1\n\n - 2\n\n - 3\n\n+ 4".len()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue