feat(parser): generic error type

This commit is contained in:
PoiScript 2019-10-02 16:50:36 +08:00
parent f4f0fa9828
commit 231d80b464
22 changed files with 974 additions and 799 deletions

View file

@ -1,6 +1,9 @@
use std::borrow::Cow;
use nom::{bytes::complete::tag_no_case, character::complete::alpha1, sequence::preceded, IResult};
use nom::{
bytes::complete::tag_no_case, character::complete::alpha1, error::ParseError,
sequence::preceded, IResult,
};
use crate::parsers::{line, take_lines_while};
@ -160,12 +163,20 @@ impl SourceBlock<'_> {
// TODO: fn retain_labels() -> bool { }
}
pub(crate) fn parse_block_element(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> {
#[inline]
pub fn parse_block_element(input: &str) -> Option<(&str, (&str, Option<&str>, &str))> {
parse_block_element_internal::<()>(input).ok()
}
#[inline]
fn parse_block_element_internal<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, (&'a str, Option<&'a str>, &'a str), E> {
let (input, name) = preceded(tag_no_case("#+BEGIN_"), alpha1)(input)?;
let (input, args) = line(input)?;
let end_line = format!(r"#+END_{}", name);
let end_line = format!("#+END_{}", name);
let (input, contents) =
take_lines_while(|line| !line.trim().eq_ignore_ascii_case(&end_line))(input)?;
take_lines_while(|line| !line.trim().eq_ignore_ascii_case(&end_line))(input);
let (input, _) = line(input)?;
Ok((
@ -184,22 +195,24 @@ pub(crate) fn parse_block_element(input: &str) -> IResult<&str, (&str, Option<&s
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
parse_block_element(
parse_block_element_internal::<VerboseError<&str>>(
r#"#+BEGIN_SRC
#+END_SRC"#
),
Ok(("", ("SRC".into(), None, "")))
);
assert_eq!(
parse_block_element(
parse_block_element_internal::<VerboseError<&str>>(
r#"#+begin_src
#+end_src"#
),
Ok(("", ("src".into(), None, "")))
);
assert_eq!(
parse_block_element(
parse_block_element_internal::<VerboseError<&str>>(
r#"#+BEGIN_SRC javascript
console.log('Hello World!');
#+END_SRC

View file

@ -4,11 +4,13 @@ use nom::{
bytes::complete::tag,
character::complete::{char, digit1, space0},
combinator::recognize,
error::ParseError,
sequence::separated_pair,
IResult,
};
use crate::elements::{Datetime, Timestamp};
use crate::elements::timestamp::{parse_inactive, Datetime, Timestamp};
use crate::parsers::eol;
/// Clock Element
@ -42,54 +44,8 @@ pub enum Clock<'a> {
}
impl Clock<'_> {
pub(crate) fn parse(input: &str) -> IResult<&str, Clock<'_>> {
let (input, _) = tag("CLOCK:")(input)?;
let (input, _) = space0(input)?;
let (input, timestamp) = Timestamp::parse_inactive(input)?;
match timestamp {
Timestamp::InactiveRange {
start,
end,
repeater,
delay,
} => {
let (input, _) = space0(input)?;
let (input, _) = tag("=>")(input)?;
let (input, _) = space0(input)?;
let (input, duration) =
recognize(separated_pair(digit1, char(':'), digit1))(input)?;
let (input, _) = eol(input)?;
Ok((
input,
Clock::Closed {
start,
end,
repeater,
delay,
duration: duration.into(),
},
))
}
Timestamp::Inactive {
start,
repeater,
delay,
} => {
let (input, _) = eol(input)?;
Ok((
input,
Clock::Running {
start,
repeater,
delay,
},
))
}
_ => unreachable!(
"`Timestamp::parse_inactive` only returns `Timestamp::InactiveRange` or `Timestamp::Inactive`."
),
}
pub(crate) fn parse(input: &str) -> Option<(&str, Clock<'_>)> {
parse_clock::<()>(input).ok()
}
pub fn into_onwed(self) -> Clock<'static> {
@ -171,10 +127,61 @@ impl Clock<'_> {
}
}
fn parse_clock<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Clock<'_>, E> {
let (input, _) = tag("CLOCK:")(input)?;
let (input, _) = space0(input)?;
let (input, timestamp) = parse_inactive(input)?;
match timestamp {
Timestamp::InactiveRange {
start,
end,
repeater,
delay,
} => {
let (input, _) = space0(input)?;
let (input, _) = tag("=>")(input)?;
let (input, _) = space0(input)?;
let (input, duration) = recognize(separated_pair(digit1, char(':'), digit1))(input)?;
let (input, _) = eol(input)?;
Ok((
input,
Clock::Closed {
start,
end,
repeater,
delay,
duration: duration.into(),
},
))
}
Timestamp::Inactive {
start,
repeater,
delay,
} => {
let (input, _) = eol(input)?;
Ok((
input,
Clock::Running {
start,
repeater,
delay,
},
))
}
_ => unreachable!(
"`parse_inactive` only returns `Timestamp::InactiveRange` or `Timestamp::Inactive`."
),
}
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"),
parse_clock::<VerboseError<&str>>("CLOCK: [2003-09-16 Tue 09:39]"),
Ok((
"",
Clock::Running {
@ -192,7 +199,9 @@ fn parse() {
))
);
assert_eq!(
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"),
parse_clock::<VerboseError<&str>>(
"CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"
),
Ok((
"",
Clock::Closed {

View file

@ -5,6 +5,7 @@ use nom::{
bytes::complete::tag,
character::complete::digit0,
combinator::recognize,
error::ParseError,
sequence::{delimited, pair, separated_pair},
IResult,
};
@ -19,23 +20,8 @@ pub struct Cookie<'a> {
}
impl Cookie<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Cookie<'_>> {
let (input, value) = recognize(delimited(
tag("["),
alt((
separated_pair(digit0, tag("/"), digit0),
pair(digit0, tag("%")),
)),
tag("]"),
))(input)?;
Ok((
input,
Cookie {
value: value.into(),
},
))
pub(crate) fn parse(input: &str) -> Option<(&str, Cookie<'_>)> {
parse_cookie::<()>(input).ok()
}
pub fn into_owned(self) -> Cookie<'static> {
@ -45,10 +31,31 @@ impl Cookie<'_> {
}
}
#[inline]
fn parse_cookie<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Cookie<'a>, E> {
let (input, value) = recognize(delimited(
tag("["),
alt((
separated_pair(digit0, tag("/"), digit0),
pair(digit0, tag("%")),
)),
tag("]"),
))(input)?;
Ok((
input,
Cookie {
value: value.into(),
},
))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
Cookie::parse("[1/10]"),
parse_cookie::<VerboseError<&str>>("[1/10]"),
Ok((
"",
Cookie {
@ -57,7 +64,7 @@ fn parse() {
))
);
assert_eq!(
Cookie::parse("[1/1000]"),
parse_cookie::<VerboseError<&str>>("[1/1000]"),
Ok((
"",
Cookie {
@ -66,7 +73,7 @@ fn parse() {
))
);
assert_eq!(
Cookie::parse("[10%]"),
parse_cookie::<VerboseError<&str>>("[10%]"),
Ok((
"",
Cookie {
@ -75,7 +82,7 @@ fn parse() {
))
);
assert_eq!(
Cookie::parse("[%]"),
parse_cookie::<VerboseError<&str>>("[%]"),
Ok((
"",
Cookie {
@ -84,7 +91,7 @@ fn parse() {
))
);
assert_eq!(
Cookie::parse("[/]"),
parse_cookie::<VerboseError<&str>>("[/]"),
Ok((
"",
Cookie {
@ -93,7 +100,7 @@ fn parse() {
))
);
assert_eq!(
Cookie::parse("[100/]"),
parse_cookie::<VerboseError<&str>>("[100/]"),
Ok((
"",
Cookie {
@ -102,7 +109,7 @@ fn parse() {
))
);
assert_eq!(
Cookie::parse("[/100]"),
parse_cookie::<VerboseError<&str>>("[/100]"),
Ok((
"",
Cookie {
@ -111,8 +118,8 @@ fn parse() {
))
);
assert!(Cookie::parse("[10% ]").is_err());
assert!(Cookie::parse("[1//100]").is_err());
assert!(Cookie::parse("[1\\100]").is_err());
assert!(Cookie::parse("[10%%]").is_err());
assert!(parse_cookie::<VerboseError<&str>>("[10% ]").is_err());
assert!(parse_cookie::<VerboseError<&str>>("[1//100]").is_err());
assert!(parse_cookie::<VerboseError<&str>>("[1\\100]").is_err());
assert!(parse_cookie::<VerboseError<&str>>("[10%%]").is_err());
}

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take_while1},
error::ParseError,
sequence::delimited,
IResult,
};
@ -18,19 +19,8 @@ pub struct Drawer<'a> {
}
impl Drawer<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, (Drawer<'_>, &str)> {
let (input, name) = delimited(
tag(":"),
take_while1(|c: char| c.is_ascii_alphabetic() || c == '-' || c == '_'),
tag(":"),
)(input)?;
let (input, _) = eol(input)?;
let (input, contents) =
take_lines_while(|line| !line.trim().eq_ignore_ascii_case(":END:"))(input)?;
let (input, _) = line(input)?;
Ok((input, (Drawer { name: name.into() }, contents)))
pub(crate) fn parse(input: &str) -> Option<(&str, (Drawer<'_>, &str))> {
parse_drawer::<()>(input).ok()
}
pub fn into_owned(self) -> Drawer<'static> {
@ -40,10 +30,29 @@ impl Drawer<'_> {
}
}
#[inline]
pub fn parse_drawer<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, (Drawer<'a>, &'a str), E> {
let (input, name) = delimited(
tag(":"),
take_while1(|c: char| c.is_ascii_alphabetic() || c == '-' || c == '_'),
tag(":"),
)(input)?;
let (input, _) = eol(input)?;
let (input, contents) =
take_lines_while(|line| !line.trim().eq_ignore_ascii_case(":END:"))(input);
let (input, _) = line(input)?;
Ok((input, (Drawer { name: name.into() }, contents)))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
Drawer::parse(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
parse_drawer::<VerboseError<&str>>(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
Ok((
"",
(

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::tag_no_case,
character::complete::{alpha1, space1},
error::ParseError,
IResult,
};
@ -21,30 +22,8 @@ pub struct DynBlock<'a> {
}
impl DynBlock<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, (DynBlock<'_>, &str)> {
let (input, _) = tag_no_case("#+BEGIN:")(input)?;
let (input, _) = space1(input)?;
let (input, name) = alpha1(input)?;
let (input, args) = line(input)?;
let (input, contents) =
take_lines_while(|line| !line.trim().eq_ignore_ascii_case("#+END:"))(input)?;
let (input, _) = line(input)?;
Ok((
input,
(
DynBlock {
block_name: name.into(),
arguments: if args.trim().is_empty() {
None
} else {
Some(args.trim().into())
},
},
contents,
),
))
pub(crate) fn parse(input: &str) -> Option<(&str, (DynBlock<'_>, &str))> {
parse_dyn_block::<()>(input).ok()
}
pub fn into_owned(self) -> DynBlock<'static> {
@ -55,11 +34,41 @@ impl DynBlock<'_> {
}
}
#[inline]
fn parse_dyn_block<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, (DynBlock<'a>, &'a str), E> {
let (input, _) = tag_no_case("#+BEGIN:")(input)?;
let (input, _) = space1(input)?;
let (input, name) = alpha1(input)?;
let (input, args) = line(input)?;
let (input, contents) =
take_lines_while(|line| !line.trim().eq_ignore_ascii_case("#+END:"))(input);
let (input, _) = line(input)?;
Ok((
input,
(
DynBlock {
block_name: name.into(),
arguments: if args.trim().is_empty() {
None
} else {
Some(args.trim().into())
},
},
contents,
),
))
}
#[test]
fn parse() {
use nom::error::VerboseError;
// TODO: testing
assert_eq!(
DynBlock::parse(
parse_dyn_block::<VerboseError<&str>>(
r#"#+BEGIN: clocktable :scope file
CONTENTS
#+END:

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take_while1},
error::ParseError,
sequence::delimited,
IResult,
};
@ -18,24 +19,8 @@ pub struct FnDef<'a> {
}
impl FnDef<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, (FnDef<'_>, &str)> {
let (input, label) = delimited(
tag("[fn:"),
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
tag("]"),
)(input)?;
let (input, content) = line(input)?;
Ok((
input,
(
FnDef {
label: label.into(),
},
content,
),
))
pub(crate) fn parse(input: &str) -> Option<(&str, (FnDef<'_>, &str))> {
parse_fn_def::<()>(input).ok()
}
pub fn into_owned(self) -> FnDef<'static> {
@ -45,14 +30,38 @@ impl FnDef<'_> {
}
}
#[inline]
fn parse_fn_def<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, (FnDef<'a>, &'a str), E> {
let (input, label) = delimited(
tag("[fn:"),
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
tag("]"),
)(input)?;
let (input, content) = line(input)?;
Ok((
input,
(
FnDef {
label: label.into(),
},
content,
),
))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
FnDef::parse("[fn:1] https://orgmode.org"),
parse_fn_def::<VerboseError<&str>>("[fn:1] https://orgmode.org"),
Ok(("", (FnDef { label: "1".into() }, " https://orgmode.org")))
);
assert_eq!(
FnDef::parse("[fn:word_1] https://orgmode.org"),
parse_fn_def::<VerboseError<&str>>("[fn:word_1] https://orgmode.org"),
Ok((
"",
(
@ -64,7 +73,7 @@ fn parse() {
))
);
assert_eq!(
FnDef::parse("[fn:WORD-1] https://orgmode.org"),
parse_fn_def::<VerboseError<&str>>("[fn:WORD-1] https://orgmode.org"),
Ok((
"",
(
@ -76,7 +85,7 @@ fn parse() {
))
);
assert_eq!(
FnDef::parse("[fn:WORD]"),
parse_fn_def::<VerboseError<&str>>("[fn:WORD]"),
Ok((
"",
(
@ -88,7 +97,7 @@ fn parse() {
))
);
assert!(FnDef::parse("[fn:] https://orgmode.org").is_err());
assert!(FnDef::parse("[fn:wor d] https://orgmode.org").is_err());
assert!(FnDef::parse("[fn:WORD https://orgmode.org").is_err());
assert!(parse_fn_def::<VerboseError<&str>>("[fn:] https://orgmode.org").is_err());
assert!(parse_fn_def::<VerboseError<&str>>("[fn:wor d] https://orgmode.org").is_err());
assert!(parse_fn_def::<VerboseError<&str>>("[fn:WORD https://orgmode.org").is_err());
}

View file

@ -4,8 +4,7 @@ use memchr::memchr2_iter;
use nom::{
bytes::complete::{tag, take_while},
combinator::opt,
error::ErrorKind,
error_position,
error::{ErrorKind, ParseError},
sequence::preceded,
Err, IResult,
};
@ -21,36 +20,9 @@ pub struct FnRef<'a> {
pub definition: Option<Cow<'a, str>>,
}
fn balanced_brackets(input: &str) -> IResult<&str, &str> {
let mut pairs = 1;
for i in memchr2_iter(b'[', b']', input.as_bytes()) {
if input.as_bytes()[i] == b'[' {
pairs += 1;
} else if pairs != 1 {
pairs -= 1;
} else {
return Ok((&input[i..], &input[0..i]));
}
}
Err(Err::Error(error_position!(input, ErrorKind::Tag)))
}
impl FnRef<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, FnRef<'_>> {
let (input, _) = tag("[fn:")(input)?;
let (input, label) =
take_while(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_')(input)?;
let (input, definition) = opt(preceded(tag(":"), balanced_brackets))(input)?;
let (input, _) = tag("]")(input)?;
Ok((
input,
FnRef {
label: label.into(),
definition: definition.map(Into::into),
},
))
pub(crate) fn parse(input: &str) -> Option<(&str, FnRef<'_>)> {
parse_fn_ref::<()>(input).ok()
}
pub fn into_owned(self) -> FnRef<'static> {
@ -61,10 +33,43 @@ impl FnRef<'_> {
}
}
#[inline]
fn parse_fn_ref<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, FnRef<'a>, E> {
let (input, _) = tag("[fn:")(input)?;
let (input, label) =
take_while(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_')(input)?;
let (input, definition) = opt(preceded(tag(":"), balanced_brackets))(input)?;
let (input, _) = tag("]")(input)?;
Ok((
input,
FnRef {
label: label.into(),
definition: definition.map(Into::into),
},
))
}
fn balanced_brackets<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
let mut pairs = 1;
for i in memchr2_iter(b'[', b']', input.as_bytes()) {
if input.as_bytes()[i] == b'[' {
pairs += 1;
} else if pairs != 1 {
pairs -= 1;
} else {
return Ok((&input[i..], &input[0..i]));
}
}
Err(Err::Error(E::from_error_kind(input, ErrorKind::Tag)))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
FnRef::parse("[fn:1]"),
parse_fn_ref::<VerboseError<&str>>("[fn:1]"),
Ok((
"",
FnRef {
@ -74,7 +79,7 @@ fn parse() {
))
);
assert_eq!(
FnRef::parse("[fn:1:2]"),
parse_fn_ref::<VerboseError<&str>>("[fn:1:2]"),
Ok((
"",
FnRef {
@ -84,7 +89,7 @@ fn parse() {
))
);
assert_eq!(
FnRef::parse("[fn::2]"),
parse_fn_ref::<VerboseError<&str>>("[fn::2]"),
Ok((
"",
FnRef {
@ -94,7 +99,7 @@ fn parse() {
))
);
assert_eq!(
FnRef::parse("[fn::[]]"),
parse_fn_ref::<VerboseError<&str>>("[fn::[]]"),
Ok((
"",
FnRef {
@ -104,5 +109,5 @@ fn parse() {
))
);
assert!(FnRef::parse("[fn::[]").is_err());
assert!(parse_fn_ref::<VerboseError<&str>>("[fn::[]").is_err());
}

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take_till},
combinator::opt,
error::ParseError,
sequence::{delimited, preceded},
IResult,
};
@ -25,34 +26,8 @@ pub struct InlineCall<'a> {
}
impl InlineCall<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, InlineCall<'_>> {
let (input, name) = preceded(
tag("call_"),
take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')'),
)(input)?;
let (input, inside_header) = opt(delimited(
tag("["),
take_till(|c| c == ']' || c == '\n'),
tag("]"),
))(input)?;
let (input, arguments) =
delimited(tag("("), take_till(|c| c == ')' || c == '\n'), tag(")"))(input)?;
let (input, end_header) = opt(delimited(
tag("["),
take_till(|c| c == ']' || c == '\n'),
tag("]"),
))(input)?;
Ok((
input,
InlineCall {
name: name.into(),
arguments: arguments.into(),
inside_header: inside_header.map(Into::into),
end_header: end_header.map(Into::into),
},
))
pub(crate) fn parse(input: &str) -> Option<(&str, InlineCall<'_>)> {
parse_inline_call::<()>(input).ok()
}
pub fn into_owned(self) -> InlineCall<'static> {
@ -65,10 +40,44 @@ impl InlineCall<'_> {
}
}
#[inline]
fn parse_inline_call<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, InlineCall<'a>, E> {
let (input, name) = preceded(
tag("call_"),
take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')'),
)(input)?;
let (input, inside_header) = opt(delimited(
tag("["),
take_till(|c| c == ']' || c == '\n'),
tag("]"),
))(input)?;
let (input, arguments) =
delimited(tag("("), take_till(|c| c == ')' || c == '\n'), tag(")"))(input)?;
let (input, end_header) = opt(delimited(
tag("["),
take_till(|c| c == ']' || c == '\n'),
tag("]"),
))(input)?;
Ok((
input,
InlineCall {
name: name.into(),
arguments: arguments.into(),
inside_header: inside_header.map(Into::into),
end_header: end_header.map(Into::into),
},
))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
InlineCall::parse("call_square(4)"),
parse_inline_call::<VerboseError<&str>>("call_square(4)"),
Ok((
"",
InlineCall {
@ -80,7 +89,7 @@ fn parse() {
))
);
assert_eq!(
InlineCall::parse("call_square[:results output](4)"),
parse_inline_call::<VerboseError<&str>>("call_square[:results output](4)"),
Ok((
"",
InlineCall {
@ -92,7 +101,7 @@ fn parse() {
))
);
assert_eq!(
InlineCall::parse("call_square(4)[:results html]"),
parse_inline_call::<VerboseError<&str>>("call_square(4)[:results html]"),
Ok((
"",
InlineCall {
@ -104,7 +113,7 @@ fn parse() {
))
);
assert_eq!(
InlineCall::parse("call_square[:results output](4)[:results html]"),
parse_inline_call::<VerboseError<&str>>("call_square[:results output](4)[:results html]"),
Ok((
"",
InlineCall {

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take_till, take_while1},
combinator::opt,
error::ParseError,
sequence::delimited,
IResult,
};
@ -22,27 +23,8 @@ pub struct InlineSrc<'a> {
}
impl InlineSrc<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, InlineSrc<'_>> {
let (input, _) = tag("src_")(input)?;
let (input, lang) =
take_while1(|c: char| !c.is_ascii_whitespace() && c != '[' && c != '{')(input)?;
let (input, options) = opt(delimited(
tag("["),
take_till(|c| c == '\n' || c == ']'),
tag("]"),
))(input)?;
let (input, body) =
delimited(tag("{"), take_till(|c| c == '\n' || c == '}'), tag("}"))(input)?;
Ok((
input,
InlineSrc {
lang: lang.into(),
options: options.map(Into::into),
body: body.into(),
},
))
pub(crate) fn parse(input: &str) -> Option<(&str, InlineSrc<'_>)> {
parse_inline_src::<()>(input).ok()
}
pub fn into_owned(self) -> InlineSrc<'static> {
@ -54,10 +36,36 @@ impl InlineSrc<'_> {
}
}
#[inline]
fn parse_inline_src<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, InlineSrc<'a>, E> {
let (input, _) = tag("src_")(input)?;
let (input, lang) =
take_while1(|c: char| !c.is_ascii_whitespace() && c != '[' && c != '{')(input)?;
let (input, options) = opt(delimited(
tag("["),
take_till(|c| c == '\n' || c == ']'),
tag("]"),
))(input)?;
let (input, body) = delimited(tag("{"), take_till(|c| c == '\n' || c == '}'), tag("}"))(input)?;
Ok((
input,
InlineSrc {
lang: lang.into(),
options: options.map(Into::into),
body: body.into(),
},
))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
InlineSrc::parse("src_C{int a = 0;}"),
parse_inline_src::<VerboseError<&str>>("src_C{int a = 0;}"),
Ok((
"",
InlineSrc {
@ -68,7 +76,7 @@ fn parse() {
))
);
assert_eq!(
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>}"),
parse_inline_src::<VerboseError<&str>>("src_xml[:exports code]{<tag>text</tag>}"),
Ok((
"",
InlineSrc {
@ -79,7 +87,11 @@ fn parse() {
))
);
assert!(InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>").is_err());
assert!(InlineSrc::parse("src_[:exports code]{<tag>text</tag>}").is_err());
assert!(InlineSrc::parse("src_xml[:exports code]").is_err());
assert!(
parse_inline_src::<VerboseError<&str>>("src_xml[:exports code]{<tag>text</tag>").is_err()
);
assert!(
parse_inline_src::<VerboseError<&str>>("src_[:exports code]{<tag>text</tag>}").is_err()
);
assert!(parse_inline_src::<VerboseError<&str>>("src_xml[:exports code]").is_err());
}

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take_till},
combinator::opt,
error::ParseError,
sequence::delimited,
IResult,
};
@ -48,7 +49,15 @@ impl BabelCall<'_> {
}
}
pub(crate) fn parse_keyword(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> {
#[inline]
pub fn parse_keyword(input: &str) -> Option<(&str, (&str, Option<&str>, &str))> {
parse_keyword_internal::<()>(input).ok()
}
#[inline]
fn parse_keyword_internal<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, (&'a str, Option<&'a str>, &'a str), E> {
let (input, _) = tag("#+")(input)?;
let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
let (input, optional) = opt(delimited(
@ -64,36 +73,44 @@ pub(crate) fn parse_keyword(input: &str) -> IResult<&str, (&str, Option<&str>, &
#[test]
fn parse() {
assert_eq!(parse_keyword("#+KEY:"), Ok(("", ("KEY", None, ""))));
use nom::error::VerboseError;
assert_eq!(
parse_keyword("#+KEY: VALUE"),
parse_keyword_internal::<VerboseError<&str>>("#+KEY:"),
Ok(("", ("KEY", None, "")))
);
assert_eq!(
parse_keyword_internal::<VerboseError<&str>>("#+KEY: VALUE"),
Ok(("", ("KEY", None, "VALUE")))
);
assert_eq!(
parse_keyword("#+K_E_Y: VALUE"),
parse_keyword_internal::<VerboseError<&str>>("#+K_E_Y: VALUE"),
Ok(("", ("K_E_Y", None, "VALUE")))
);
assert_eq!(
parse_keyword("#+KEY:VALUE\n"),
parse_keyword_internal::<VerboseError<&str>>("#+KEY:VALUE\n"),
Ok(("", ("KEY", None, "VALUE")))
);
assert!(parse_keyword("#+KE Y: VALUE").is_err());
assert!(parse_keyword("#+ KEY: VALUE").is_err());
assert_eq!(parse_keyword("#+RESULTS:"), Ok(("", ("RESULTS", None, ""))));
assert!(parse_keyword_internal::<VerboseError<&str>>("#+KE Y: VALUE").is_err());
assert!(parse_keyword_internal::<VerboseError<&str>>("#+ KEY: VALUE").is_err());
assert_eq!(
parse_keyword("#+ATTR_LATEX: :width 5cm\n"),
parse_keyword_internal::<VerboseError<&str>>("#+RESULTS:"),
Ok(("", ("RESULTS", None, "")))
);
assert_eq!(
parse_keyword_internal::<VerboseError<&str>>("#+ATTR_LATEX: :width 5cm\n"),
Ok(("", ("ATTR_LATEX", None, ":width 5cm")))
);
assert_eq!(
parse_keyword("#+CALL: double(n=4)"),
parse_keyword_internal::<VerboseError<&str>>("#+CALL: double(n=4)"),
Ok(("", ("CALL", None, "double(n=4)")))
);
assert_eq!(
parse_keyword("#+CAPTION[Short caption]: Longer caption."),
parse_keyword_internal::<VerboseError<&str>>("#+CAPTION[Short caption]: Longer caption."),
Ok(("", ("CAPTION", Some("Short caption"), "Longer caption.",)))
);
}

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take_while},
combinator::opt,
error::ParseError,
sequence::delimited,
IResult,
};
@ -20,25 +21,8 @@ pub struct Link<'a> {
impl Link<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Link<'_>> {
let (input, path) = delimited(
tag("[["),
take_while(|c: char| c != '<' && c != '>' && c != '\n' && c != ']'),
tag("]"),
)(input)?;
let (input, desc) = opt(delimited(
tag("["),
take_while(|c: char| c != '[' && c != ']'),
tag("]"),
))(input)?;
let (input, _) = tag("]")(input)?;
Ok((
input,
Link {
path: path.into(),
desc: desc.map(Into::into),
},
))
pub(crate) fn parse(input: &str) -> Option<(&str, Link<'_>)> {
parse_link::<()>(input).ok()
}
pub fn into_owned(self) -> Link<'static> {
@ -49,10 +33,34 @@ impl Link<'_> {
}
}
#[inline]
fn parse_link<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Link<'a>, E> {
let (input, path) = delimited(
tag("[["),
take_while(|c: char| c != '<' && c != '>' && c != '\n' && c != ']'),
tag("]"),
)(input)?;
let (input, desc) = opt(delimited(
tag("["),
take_while(|c: char| c != '[' && c != ']'),
tag("]"),
))(input)?;
let (input, _) = tag("]")(input)?;
Ok((
input,
Link {
path: path.into(),
desc: desc.map(Into::into),
},
))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
Link::parse("[[#id]]"),
parse_link::<VerboseError<&str>>("[[#id]]"),
Ok((
"",
Link {
@ -62,7 +70,7 @@ fn parse() {
))
);
assert_eq!(
Link::parse("[[#id][desc]]"),
parse_link::<VerboseError<&str>>("[[#id][desc]]"),
Ok((
"",
Link {
@ -71,5 +79,5 @@ fn parse() {
}
))
);
assert!(Link::parse("[[#id][desc]").is_err());
assert!(parse_link::<VerboseError<&str>>("[[#id][desc]").is_err());
}

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take, take_until, take_while1},
combinator::{opt, verify},
error::ParseError,
sequence::delimited,
IResult,
};
@ -20,23 +21,8 @@ pub struct Macros<'a> {
}
impl Macros<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Macros<'_>> {
let (input, _) = tag("{{{")(input)?;
let (input, name) = verify(
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
|s: &str| s.starts_with(|c: char| c.is_ascii_alphabetic()),
)(input)?;
let (input, arguments) = opt(delimited(tag("("), take_until(")}}}"), take(1usize)))(input)?;
let (input, _) = tag("}}}")(input)?;
Ok((
input,
Macros {
name: name.into(),
arguments: arguments.map(Into::into),
},
))
pub(crate) fn parse(input: &str) -> Option<(&str, Macros<'_>)> {
parse_macros::<()>(input).ok()
}
pub fn into_owned(self) -> Macros<'static> {
@ -47,10 +33,31 @@ impl Macros<'_> {
}
}
#[inline]
fn parse_macros<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Macros<'a>, E> {
let (input, _) = tag("{{{")(input)?;
let (input, name) = verify(
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
|s: &str| s.starts_with(|c: char| c.is_ascii_alphabetic()),
)(input)?;
let (input, arguments) = opt(delimited(tag("("), take_until(")}}}"), take(1usize)))(input)?;
let (input, _) = tag("}}}")(input)?;
Ok((
input,
Macros {
name: name.into(),
arguments: arguments.map(Into::into),
},
))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
Macros::parse("{{{poem(red,blue)}}}"),
parse_macros::<VerboseError<&str>>("{{{poem(red,blue)}}}"),
Ok((
"",
Macros {
@ -60,7 +67,7 @@ fn parse() {
))
);
assert_eq!(
Macros::parse("{{{poem())}}}"),
parse_macros::<VerboseError<&str>>("{{{poem())}}}"),
Ok((
"",
Macros {
@ -70,7 +77,7 @@ fn parse() {
))
);
assert_eq!(
Macros::parse("{{{author}}}"),
parse_macros::<VerboseError<&str>>("{{{author}}}"),
Ok((
"",
Macros {
@ -79,8 +86,8 @@ fn parse() {
}
))
);
assert!(Macros::parse("{{{0uthor}}}").is_err());
assert!(Macros::parse("{{{author}}").is_err());
assert!(Macros::parse("{{{poem(}}}").is_err());
assert!(Macros::parse("{{{poem)}}}").is_err());
assert!(parse_macros::<VerboseError<&str>>("{{{0uthor}}}").is_err());
assert!(parse_macros::<VerboseError<&str>>("{{{author}}").is_err());
assert!(parse_macros::<VerboseError<&str>>("{{{poem(}}}").is_err());
assert!(parse_macros::<VerboseError<&str>>("{{{poem)}}}").is_err());
}

View file

@ -1,32 +1,27 @@
//! Org-mode elements
mod block;
mod clock;
mod cookie;
mod drawer;
mod dyn_block;
mod emphasis;
mod fn_def;
mod fn_ref;
mod inline_call;
mod inline_src;
mod keyword;
mod link;
mod list;
mod macros;
mod planning;
mod radio_target;
mod rule;
mod snippet;
mod table;
mod target;
mod timestamp;
mod title;
pub(crate) use self::{
block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword,
radio_target::parse_radio_target, rule::parse_rule, table::parse_table_el,
};
pub(crate) mod block;
pub(crate) mod clock;
pub(crate) mod cookie;
pub(crate) mod drawer;
pub(crate) mod dyn_block;
pub(crate) mod emphasis;
pub(crate) mod fn_def;
pub(crate) mod fn_ref;
pub(crate) mod inline_call;
pub(crate) mod inline_src;
pub(crate) mod keyword;
pub(crate) mod link;
pub(crate) mod list;
pub(crate) mod macros;
pub(crate) mod planning;
pub(crate) mod radio_target;
pub(crate) mod rule;
pub(crate) mod snippet;
pub(crate) mod table;
pub(crate) mod target;
pub(crate) mod timestamp;
pub(crate) mod title;
pub use self::{
block::{

View file

@ -30,23 +30,18 @@ impl Planning<'_> {
let next = &tail[i + 1..].trim_start();
macro_rules! set_timestamp {
($timestamp:expr) => {
if $timestamp.is_none() {
let (new_tail, timestamp) = Timestamp::parse_active(next)
.or_else(|_| Timestamp::parse_inactive(next))
.ok()?;
$timestamp = Some(timestamp);
tail = new_tail.trim_start();
} else {
return None;
}
};
($timestamp:expr) => {{
let (new_tail, timestamp) =
Timestamp::parse_active(next).or(Timestamp::parse_inactive(next))?;
$timestamp = Some(timestamp);
tail = new_tail.trim_start();
}};
}
match &tail[..i] {
"DEADLINE:" => set_timestamp!(deadline),
"SCHEDULED:" => set_timestamp!(scheduled),
"CLOSED:" => set_timestamp!(closed),
"DEADLINE:" if deadline.is_none() => set_timestamp!(deadline),
"SCHEDULED:" if scheduled.is_none() => set_timestamp!(scheduled),
"CLOSED:" if closed.is_none() => set_timestamp!(closed),
_ => return None,
}
}

View file

@ -1,6 +1,7 @@
use nom::{
bytes::complete::{tag, take_while},
combinator::verify,
error::ParseError,
sequence::delimited,
IResult,
};
@ -8,7 +9,14 @@ use nom::{
// TODO: text-markup, entities, latex-fragments, subscript and superscript
#[inline]
pub(crate) fn parse_radio_target(input: &str) -> IResult<&str, &str> {
pub fn parse_radio_target(input: &str) -> Option<(&str, &str)> {
parse_radio_target_internal::<()>(input).ok()
}
#[inline]
fn parse_radio_target_internal<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, &'a str, E> {
let (input, contents) = delimited(
tag("<<<"),
verify(
@ -23,12 +31,20 @@ pub(crate) fn parse_radio_target(input: &str) -> IResult<&str, &str> {
#[test]
fn parse() {
assert_eq!(parse_radio_target("<<<target>>>"), Ok(("", "target")));
assert_eq!(parse_radio_target("<<<tar get>>>"), Ok(("", "tar get")));
assert!(parse_radio_target("<<<target >>>").is_err());
assert!(parse_radio_target("<<< target>>>").is_err());
assert!(parse_radio_target("<<<ta<get>>>").is_err());
assert!(parse_radio_target("<<<ta>get>>>").is_err());
assert!(parse_radio_target("<<<ta\nget>>>").is_err());
assert!(parse_radio_target("<<<target>>").is_err());
use nom::error::VerboseError;
assert_eq!(
parse_radio_target_internal::<VerboseError<&str>>("<<<target>>>"),
Ok(("", "target"))
);
assert_eq!(
parse_radio_target_internal::<VerboseError<&str>>("<<<tar get>>>"),
Ok(("", "tar get"))
);
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<target >>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<< target>>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<ta<get>>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<ta>get>>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<ta\nget>>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<target>>").is_err());
}

View file

@ -1,10 +1,16 @@
use std::usize;
use nom::{bytes::complete::take_while_m_n, IResult};
use nom::{bytes::complete::take_while_m_n, error::ParseError, IResult};
use crate::parsers::eol;
pub(crate) fn parse_rule(input: &str) -> IResult<&str, ()> {
pub fn parse_rule(input: &str) -> Option<&str> {
parse_rule_internal::<()>(input)
.ok()
.map(|(input, _)| input)
}
fn parse_rule_internal<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, (), E> {
let (input, _) = take_while_m_n(5, usize::MAX, |c| c == '-')(input)?;
let (input, _) = eol(input)?;
Ok((input, ()))
@ -12,18 +18,32 @@ pub(crate) fn parse_rule(input: &str) -> IResult<&str, ()> {
#[test]
fn parse() {
assert_eq!(parse_rule("-----"), Ok(("", ())));
assert_eq!(parse_rule("--------"), Ok(("", ())));
assert_eq!(parse_rule("-----\n"), Ok(("", ())));
assert_eq!(parse_rule("----- \n"), Ok(("", ())));
assert!(parse_rule("").is_err());
assert!(parse_rule("----").is_err());
assert!(parse_rule("----").is_err());
assert!(parse_rule("None----").is_err());
assert!(parse_rule("None ----").is_err());
assert!(parse_rule("None------").is_err());
assert!(parse_rule("----None----").is_err());
assert!(parse_rule("\t\t----").is_err());
assert!(parse_rule("------None").is_err());
assert!(parse_rule("----- None").is_err());
use nom::error::VerboseError;
assert_eq!(
parse_rule_internal::<VerboseError<&str>>("-----"),
Ok(("", ()))
);
assert_eq!(
parse_rule_internal::<VerboseError<&str>>("--------"),
Ok(("", ()))
);
assert_eq!(
parse_rule_internal::<VerboseError<&str>>("-----\n"),
Ok(("", ()))
);
assert_eq!(
parse_rule_internal::<VerboseError<&str>>("----- \n"),
Ok(("", ()))
);
assert!(parse_rule_internal::<VerboseError<&str>>("").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("None----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("None ----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("None------").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("----None----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("\t\t----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("------None").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("----- None").is_err());
}

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take, take_until, take_while1},
error::ParseError,
sequence::{delimited, separated_pair},
IResult,
};
@ -18,25 +19,8 @@ pub struct Snippet<'a> {
}
impl Snippet<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Snippet<'_>> {
let (input, (name, value)) = delimited(
tag("@@"),
separated_pair(
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-'),
tag(":"),
take_until("@@"),
),
take(2usize),
)(input)?;
Ok((
input,
Snippet {
name: name.into(),
value: value.into(),
},
))
pub(crate) fn parse(input: &str) -> Option<(&str, Snippet<'_>)> {
parse_snippet::<()>(input).ok()
}
pub fn into_owned(self) -> Snippet<'static> {
@ -47,10 +31,33 @@ impl Snippet<'_> {
}
}
#[inline]
fn parse_snippet<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Snippet<'a>, E> {
let (input, (name, value)) = delimited(
tag("@@"),
separated_pair(
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-'),
tag(":"),
take_until("@@"),
),
take(2usize),
)(input)?;
Ok((
input,
Snippet {
name: name.into(),
value: value.into(),
},
))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
Snippet::parse("@@html:<b>@@"),
parse_snippet::<VerboseError<&str>>("@@html:<b>@@"),
Ok((
"",
Snippet {
@ -60,7 +67,7 @@ fn parse() {
))
);
assert_eq!(
Snippet::parse("@@latex:any arbitrary LaTeX code@@"),
parse_snippet::<VerboseError<&str>>("@@latex:any arbitrary LaTeX code@@"),
Ok((
"",
Snippet {
@ -70,7 +77,7 @@ fn parse() {
))
);
assert_eq!(
Snippet::parse("@@html:@@"),
parse_snippet::<VerboseError<&str>>("@@html:@@"),
Ok((
"",
Snippet {
@ -80,7 +87,7 @@ fn parse() {
))
);
assert_eq!(
Snippet::parse("@@html:<p>@</p>@@"),
parse_snippet::<VerboseError<&str>>("@@html:<p>@</p>@@"),
Ok((
"",
Snippet {
@ -89,7 +96,7 @@ fn parse() {
}
))
);
assert!(Snippet::parse("@@html:<b>@").is_err());
assert!(Snippet::parse("@@html<b>@@").is_err());
assert!(Snippet::parse("@@:<b>@@").is_err());
assert!(parse_snippet::<VerboseError<&str>>("@@html:<b>@").is_err());
assert!(parse_snippet::<VerboseError<&str>>("@@html<b>@@").is_err());
assert!(parse_snippet::<VerboseError<&str>>("@@:<b>@@").is_err());
}

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use nom::{
combinator::{peek, verify},
error::ParseError,
IResult,
};
@ -57,19 +58,30 @@ impl TableRow {
}
}
pub(crate) fn parse_table_el(input: &str) -> IResult<&str, &str> {
pub(crate) fn parse_table_el(input: &str) -> Option<(&str, &str)> {
parse_table_el_internal::<()>(input).ok()
}
fn parse_table_el_internal<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, &'a str, E> {
let (input, _) = peek(verify(line, |s: &str| {
let s = s.trim();
s.starts_with("+-") && s.as_bytes().iter().all(|&c| c == b'+' || c == b'-')
}))(input)?;
take_lines_while(|line| line.starts_with('|') || line.starts_with('+'))(input)
let (input, content) =
take_lines_while(|line| line.starts_with('|') || line.starts_with('+'))(input);
Ok((input, content))
}
#[test]
fn parse_table_el_() {
use nom::error::VerboseError;
assert_eq!(
parse_table_el(
parse_table_el_internal::<VerboseError<&str>>(
r#"+---+
| |
+---+
@ -85,6 +97,6 @@ fn parse_table_el_() {
"#
))
);
assert!(parse_table_el("").is_err());
assert!(parse_table_el("+----|---").is_err());
assert!(parse_table_el_internal::<VerboseError<&str>>("").is_err());
assert!(parse_table_el_internal::<VerboseError<&str>>("+----|---").is_err());
}

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{
bytes::complete::{tag, take_while},
combinator::verify,
error::ParseError,
sequence::delimited,
IResult,
};
@ -18,22 +19,8 @@ pub struct Target<'a> {
impl Target<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Target<'_>> {
let (input, target) = delimited(
tag("<<"),
verify(
take_while(|c: char| c != '<' && c != '\n' && c != '>'),
|s: &str| s.starts_with(|c| c != ' ') && s.ends_with(|c| c != ' '),
),
tag(">>"),
)(input)?;
Ok((
input,
Target {
target: target.into(),
},
))
pub(crate) fn parse(input: &str) -> Option<(&str, Target<'_>)> {
parse_target::<()>(input).ok()
}
pub fn into_owned(self) -> Target<'static> {
@ -43,10 +30,31 @@ impl Target<'_> {
}
}
#[inline]
fn parse_target<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Target<'a>, E> {
let (input, target) = delimited(
tag("<<"),
verify(
take_while(|c: char| c != '<' && c != '\n' && c != '>'),
|s: &str| s.starts_with(|c| c != ' ') && s.ends_with(|c| c != ' '),
),
tag(">>"),
)(input)?;
Ok((
input,
Target {
target: target.into(),
},
))
}
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
Target::parse("<<target>>"),
parse_target::<VerboseError<&str>>("<<target>>"),
Ok((
"",
Target {
@ -55,7 +63,7 @@ fn parse() {
))
);
assert_eq!(
Target::parse("<<tar get>>"),
parse_target::<VerboseError<&str>>("<<tar get>>"),
Ok((
"",
Target {
@ -63,10 +71,10 @@ fn parse() {
}
))
);
assert!(Target::parse("<<target >>").is_err());
assert!(Target::parse("<< target>>").is_err());
assert!(Target::parse("<<ta<get>>").is_err());
assert!(Target::parse("<<ta>get>>").is_err());
assert!(Target::parse("<<ta\nget>>").is_err());
assert!(Target::parse("<<target>").is_err());
assert!(parse_target::<VerboseError<&str>>("<<target >>").is_err());
assert!(parse_target::<VerboseError<&str>>("<< target>>").is_err());
assert!(parse_target::<VerboseError<&str>>("<<ta<get>>").is_err());
assert!(parse_target::<VerboseError<&str>>("<<ta>get>>").is_err());
assert!(parse_target::<VerboseError<&str>>("<<ta\nget>>").is_err());
assert!(parse_target::<VerboseError<&str>>("<<target>").is_err());
}

View file

@ -4,6 +4,7 @@ use nom::{
bytes::complete::{tag, take, take_till, take_while, take_while_m_n},
character::complete::{space0, space1},
combinator::{map, map_res, opt},
error::ParseError,
sequence::preceded,
IResult,
};
@ -137,123 +138,16 @@ pub enum Timestamp<'a> {
}
impl Timestamp<'_> {
pub(crate) fn parse_active(input: &str) -> IResult<&str, Timestamp<'_>> {
let (input, _) = tag("<")(input)?;
let (input, start) = parse_datetime(input)?;
if input.starts_with('-') {
let (input, (hour, minute)) = parse_time(&input[1..])?;
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag(">")(input)?;
let mut end = start.clone();
end.hour = Some(hour);
end.minute = Some(minute);
return Ok((
input,
Timestamp::ActiveRange {
start,
end,
repeater: None,
delay: None,
},
));
}
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag(">")(input)?;
if input.starts_with("--<") {
let (input, end) = parse_datetime(&input["--<".len()..])?;
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag(">")(input)?;
Ok((
input,
Timestamp::ActiveRange {
start,
end,
repeater: None,
delay: None,
},
))
} else {
Ok((
input,
Timestamp::Active {
start,
repeater: None,
delay: None,
},
))
}
pub(crate) fn parse_active(input: &str) -> Option<(&str, Timestamp<'_>)> {
parse_active::<()>(input).ok()
}
pub(crate) fn parse_inactive(input: &str) -> IResult<&str, Timestamp<'_>> {
let (input, _) = tag("[")(input)?;
let (input, start) = parse_datetime(input)?;
if input.starts_with('-') {
let (input, (hour, minute)) = parse_time(&input[1..])?;
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag("]")(input)?;
let mut end = start.clone();
end.hour = Some(hour);
end.minute = Some(minute);
return Ok((
input,
Timestamp::InactiveRange {
start,
end,
repeater: None,
delay: None,
},
));
}
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag("]")(input)?;
if input.starts_with("--[") {
let (input, end) = parse_datetime(&input["--[".len()..])?;
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag("]")(input)?;
Ok((
input,
Timestamp::InactiveRange {
start,
end,
repeater: None,
delay: None,
},
))
} else {
Ok((
input,
Timestamp::Inactive {
start,
repeater: None,
delay: None,
},
))
}
pub(crate) fn parse_inactive(input: &str) -> Option<(&str, Timestamp<'_>)> {
parse_inactive::<()>(input).ok()
}
pub(crate) fn parse_diary(input: &str) -> IResult<&str, Timestamp<'_>> {
let (input, _) = tag("<%%(")(input)?;
let (input, value) = take_till(|c| c == ')' || c == '>' || c == '\n')(input)?;
let (input, _) = tag(")>")(input)?;
Ok((
input,
Timestamp::Diary {
value: value.into(),
},
))
pub(crate) fn parse_diary(input: &str) -> Option<(&str, Timestamp<'_>)> {
parse_diary::<()>(input).ok()
}
pub fn into_owned(self) -> Timestamp<'static> {
@ -305,7 +199,132 @@ impl Timestamp<'_> {
}
}
fn parse_time(input: &str) -> IResult<&str, (u8, u8)> {
pub fn parse_active<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, Timestamp<'a>, E> {
let (input, _) = tag("<")(input)?;
let (input, start) = parse_datetime(input)?;
if input.starts_with('-') {
let (input, (hour, minute)) = parse_time(&input[1..])?;
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag(">")(input)?;
let mut end = start.clone();
end.hour = Some(hour);
end.minute = Some(minute);
return Ok((
input,
Timestamp::ActiveRange {
start,
end,
repeater: None,
delay: None,
},
));
}
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag(">")(input)?;
if input.starts_with("--<") {
let (input, end) = parse_datetime(&input["--<".len()..])?;
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag(">")(input)?;
Ok((
input,
Timestamp::ActiveRange {
start,
end,
repeater: None,
delay: None,
},
))
} else {
Ok((
input,
Timestamp::Active {
start,
repeater: None,
delay: None,
},
))
}
}
pub fn parse_inactive<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, Timestamp<'a>, E> {
let (input, _) = tag("[")(input)?;
let (input, start) = parse_datetime(input)?;
if input.starts_with('-') {
let (input, (hour, minute)) = parse_time(&input[1..])?;
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag("]")(input)?;
let mut end = start.clone();
end.hour = Some(hour);
end.minute = Some(minute);
return Ok((
input,
Timestamp::InactiveRange {
start,
end,
repeater: None,
delay: None,
},
));
}
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag("]")(input)?;
if input.starts_with("--[") {
let (input, end) = parse_datetime(&input["--[".len()..])?;
let (input, _) = space0(input)?;
// TODO: delay-or-repeater
let (input, _) = tag("]")(input)?;
Ok((
input,
Timestamp::InactiveRange {
start,
end,
repeater: None,
delay: None,
},
))
} else {
Ok((
input,
Timestamp::Inactive {
start,
repeater: None,
delay: None,
},
))
}
}
pub fn parse_diary<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, Timestamp<'a>, E> {
let (input, _) = tag("<%%(")(input)?;
let (input, value) = take_till(|c| c == ')' || c == '>' || c == '\n')(input)?;
let (input, _) = tag(")>")(input)?;
Ok((
input,
Timestamp::Diary {
value: value.into(),
},
))
}
fn parse_time<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, (u8, u8), E> {
let (input, hour) = map_res(take_while_m_n(1, 2, |c: char| c.is_ascii_digit()), |num| {
u8::from_str_radix(num, 10)
})(input)?;
@ -314,7 +333,7 @@ fn parse_time(input: &str) -> IResult<&str, (u8, u8)> {
Ok((input, (hour, minute)))
}
fn parse_datetime(input: &str) -> IResult<&str, Datetime<'_>> {
fn parse_datetime<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Datetime<'a>, E> {
let parse_u8 = |num| u8::from_str_radix(num, 10);
let (input, year) = map_res(take(4usize), |num| u16::from_str_radix(num, 10))(input)?;
@ -397,8 +416,10 @@ fn parse_datetime(input: &str) -> IResult<&str, Datetime<'_>> {
#[test]
fn parse() {
use nom::error::VerboseError;
assert_eq!(
Timestamp::parse_inactive("[2003-09-16 Tue]"),
parse_inactive::<VerboseError<&str>>("[2003-09-16 Tue]"),
Ok((
"",
Timestamp::Inactive {
@ -416,7 +437,7 @@ fn parse() {
))
);
assert_eq!(
Timestamp::parse_inactive("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"),
parse_inactive::<VerboseError<&str>>("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"),
Ok((
"",
Timestamp::InactiveRange {
@ -442,7 +463,7 @@ fn parse() {
))
);
assert_eq!(
Timestamp::parse_active("<2003-09-16 Tue 09:39-10:39>"),
parse_active::<VerboseError<&str>>("<2003-09-16 Tue 09:39-10:39>"),
Ok((
"",
Timestamp::ActiveRange {

View file

@ -8,15 +8,14 @@ use nom::{
bytes::complete::{tag, take_until, take_while},
character::complete::{anychar, space1},
combinator::{map, map_parser, opt, verify},
error::ErrorKind,
error_position,
error::{ErrorKind, ParseError},
multi::fold_many0,
sequence::{delimited, preceded},
Err, IResult,
};
use crate::config::ParseConfig;
use crate::elements::{Drawer, Planning, Timestamp};
use crate::elements::{drawer::parse_drawer, Planning, Timestamp};
use crate::parsers::{line, skip_empty_lines, take_one_word};
/// Title Elemenet
@ -46,68 +45,11 @@ pub struct Title<'a> {
}
impl Title<'_> {
#[inline]
pub(crate) fn parse<'a>(
input: &'a str,
config: &ParseConfig,
) -> IResult<&'a str, (Title<'a>, &'a str)> {
let (input, level) = map(take_while(|c: char| c == '*'), |s: &str| s.len())(input)?;
debug_assert!(level > 0);
let (input, keyword) = opt(preceded(
space1,
verify(take_one_word, |s: &str| {
config.todo_keywords.iter().any(|x| x == s)
|| config.done_keywords.iter().any(|x| x == s)
}),
))(input)?;
let (input, priority) = opt(preceded(
space1,
map_parser(
take_one_word,
delimited(
tag("[#"),
verify(anychar, |c: &char| c.is_ascii_uppercase()),
tag("]"),
),
),
))(input)?;
let (input, tail) = line(input)?;
let tail = tail.trim();
let (raw, tags) = memrchr(b' ', tail.as_bytes())
.map(|i| (tail[0..i].trim(), &tail[i + 1..]))
.filter(|(_, x)| x.len() > 2 && x.starts_with(':') && x.ends_with(':'))
.unwrap_or((tail, ""));
let tags = tags
.split(':')
.filter(|s| !s.is_empty())
.map(Into::into)
.collect();
let (input, planning) = Planning::parse(input)
.map(|(input, planning)| (input, Some(Box::new(planning))))
.unwrap_or((input, None));
let (input, properties) = opt(parse_properties_drawer)(input)?;
Ok((
input,
(
Title {
properties: properties.unwrap_or_default(),
level,
keyword: keyword.map(Into::into),
priority,
tags,
raw: raw.into(),
planning,
},
raw,
),
))
) -> Option<(&'a str, (Title<'a>, &'a str))> {
parse_title::<()>(input, config).ok()
}
// TODO: fn is_archived(&self) -> bool { }
@ -136,6 +78,11 @@ impl Title<'_> {
.and_then(|planning| planning.deadline.as_ref())
}
/// checks if this headline is "archived"
pub fn is_archived(&self) -> bool {
self.tags.iter().any(|tag| tag == "ARCHIVE")
}
pub fn into_owned(self) -> Title<'static> {
Title {
level: self.level,
@ -171,10 +118,77 @@ impl Default for Title<'_> {
}
}
fn parse_properties_drawer(input: &str) -> IResult<&str, HashMap<Cow<'_, str>, Cow<'_, str>>> {
let (input, (drawer, content)) = Drawer::parse(input.trim_start())?;
#[inline]
fn parse_title<'a, E: ParseError<&'a str>>(
input: &'a str,
config: &ParseConfig,
) -> IResult<&'a str, (Title<'a>, &'a str), E> {
let (input, level) = map(take_while(|c: char| c == '*'), |s: &str| s.len())(input)?;
debug_assert!(level > 0);
let (input, keyword) = opt(preceded(
space1,
verify(take_one_word, |s: &str| {
config.todo_keywords.iter().any(|x| x == s)
|| config.done_keywords.iter().any(|x| x == s)
}),
))(input)?;
let (input, priority) = opt(preceded(
space1,
map_parser(
take_one_word,
delimited(
tag("[#"),
verify(anychar, |c: &char| c.is_ascii_uppercase()),
tag("]"),
),
),
))(input)?;
let (input, tail) = line(input)?;
let tail = tail.trim();
let (raw, tags) = memrchr(b' ', tail.as_bytes())
.map(|i| (tail[0..i].trim(), &tail[i + 1..]))
.filter(|(_, x)| x.len() > 2 && x.starts_with(':') && x.ends_with(':'))
.unwrap_or((tail, ""));
let tags = tags
.split(':')
.filter(|s| !s.is_empty())
.map(Into::into)
.collect();
let (input, planning) = Planning::parse(input)
.map(|(input, planning)| (input, Some(Box::new(planning))))
.unwrap_or((input, None));
let (input, properties) = opt(parse_properties_drawer)(input)?;
Ok((
input,
(
Title {
properties: properties.unwrap_or_default(),
level,
keyword: keyword.map(Into::into),
priority,
tags,
raw: raw.into(),
planning,
},
raw,
),
))
}
#[inline]
fn parse_properties_drawer<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, HashMap<Cow<'a, str>, Cow<'a, str>>, E> {
let (input, (drawer, content)) = parse_drawer(input.trim_start())?;
if drawer.name != "PROPERTIES" {
return Err(Err::Error(error_position!(input, ErrorKind::Tag)));
return Err(Err::Error(E::from_error_kind(input, ErrorKind::Tag)));
}
let (_, map) = fold_many0(
parse_node_property,
@ -187,7 +201,10 @@ fn parse_properties_drawer(input: &str) -> IResult<&str, HashMap<Cow<'_, str>, C
Ok((input, map))
}
fn parse_node_property(input: &str) -> IResult<&str, (&str, &str)> {
#[inline]
fn parse_node_property<'a, E: ParseError<&'a str>>(
input: &'a str,
) -> IResult<&'a str, (&'a str, &'a str), E> {
let input = skip_empty_lines(input).trim_start();
let (input, name) = map(delimited(tag(":"), take_until(":"), tag(":")), |s: &str| {
s.trim_end_matches('+')
@ -196,19 +213,17 @@ fn parse_node_property(input: &str) -> IResult<&str, (&str, &str)> {
Ok((input, (name, value.trim())))
}
impl Title<'_> {
/// checks if this headline is "archived"
pub fn is_archived(&self) -> bool {
self.tags.iter().any(|tag| tag == "ARCHIVE")
}
}
#[test]
fn parse_title() {
fn parse_title_() {
use nom::error::VerboseError;
use crate::config::DEFAULT_CONFIG;
assert_eq!(
Title::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &DEFAULT_CONFIG),
parse_title::<VerboseError<&str>>(
"**** DONE [#A] COMMENT Title :tag:a2%:",
&DEFAULT_CONFIG
),
Ok((
"",
(
@ -226,7 +241,7 @@ fn parse_title() {
))
);
assert_eq!(
Title::parse("**** ToDO [#A] COMMENT Title", &DEFAULT_CONFIG),
parse_title::<VerboseError<&str>>("**** ToDO [#A] COMMENT Title", &DEFAULT_CONFIG),
Ok((
"",
(
@ -244,7 +259,7 @@ fn parse_title() {
))
);
assert_eq!(
Title::parse("**** T0DO [#A] COMMENT Title", &DEFAULT_CONFIG),
parse_title::<VerboseError<&str>>("**** T0DO [#A] COMMENT Title", &DEFAULT_CONFIG),
Ok((
"",
(
@ -262,7 +277,7 @@ fn parse_title() {
))
);
assert_eq!(
Title::parse("**** DONE [#1] COMMENT Title", &DEFAULT_CONFIG),
parse_title::<VerboseError<&str>>("**** DONE [#1] COMMENT Title", &DEFAULT_CONFIG),
Ok((
"",
(
@ -280,7 +295,7 @@ fn parse_title() {
))
);
assert_eq!(
Title::parse("**** DONE [#a] COMMENT Title", &DEFAULT_CONFIG),
parse_title::<VerboseError<&str>>("**** DONE [#a] COMMENT Title", &DEFAULT_CONFIG),
Ok((
"",
(
@ -298,7 +313,7 @@ fn parse_title() {
))
);
assert_eq!(
Title::parse("**** Title :tag:a2%", &DEFAULT_CONFIG),
parse_title::<VerboseError<&str>>("**** Title :tag:a2%", &DEFAULT_CONFIG),
Ok((
"",
(
@ -316,7 +331,7 @@ fn parse_title() {
))
);
assert_eq!(
Title::parse("**** Title tag:a2%:", &DEFAULT_CONFIG),
parse_title::<VerboseError<&str>>("**** Title tag:a2%:", &DEFAULT_CONFIG),
Ok((
"",
(
@ -335,7 +350,7 @@ fn parse_title() {
);
assert_eq!(
Title::parse(
parse_title::<VerboseError<&str>>(
"**** DONE Title",
&ParseConfig {
done_keywords: vec![],
@ -359,7 +374,7 @@ fn parse_title() {
))
);
assert_eq!(
Title::parse(
parse_title::<VerboseError<&str>>(
"**** TASK [#A] Title",
&ParseConfig {
todo_keywords: vec!["TASK".to_string()],
@ -386,8 +401,12 @@ fn parse_title() {
#[test]
fn parse_properties_drawer_() {
use nom::error::VerboseError;
assert_eq!(
parse_properties_drawer(" :PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
parse_properties_drawer::<VerboseError<&str>>(
" :PROPERTIES:\n :CUSTOM_ID: id\n :END:"
),
Ok((
"",
vec![("CUSTOM_ID".into(), "id".into())]
@ -399,14 +418,14 @@ fn parse_properties_drawer_() {
// #[test]
// fn is_commented() {
// assert!(Title::parse("* COMMENT Title", &DEFAULT_CONFIG)
// assert!(parse_title::<VerboseError<&str>>("* COMMENT Title", &DEFAULT_CONFIG)
// .1
// .is_commented());
// assert!(!Title::parse("* Title", &DEFAULT_CONFIG).1.is_commented());
// assert!(!Title::parse("* C0MMENT Title", &DEFAULT_CONFIG)
// assert!(!parse_title::<VerboseError<&str>>("* Title", &DEFAULT_CONFIG).1.is_commented());
// assert!(!parse_title::<VerboseError<&str>>("* C0MMENT Title", &DEFAULT_CONFIG)
// .1
// .is_commented());
// assert!(!Title::parse("* comment Title", &DEFAULT_CONFIG)
// assert!(!parse_title::<VerboseError<&str>>("* comment Title", &DEFAULT_CONFIG)
// .1
// .is_commented());
// }

View file

@ -7,13 +7,16 @@ use std::marker::PhantomData;
use indextree::{Arena, NodeId};
use jetscii::{bytes, BytesConst};
use memchr::{memchr, memchr_iter};
use nom::{
bytes::complete::take_while1, combinator::verify, error::ErrorKind, error_position, Err,
IResult,
};
use nom::{bytes::complete::take_while1, combinator::verify, error::ParseError, IResult};
use crate::config::ParseConfig;
use crate::elements::*;
use crate::elements::{
block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword,
radio_target::parse_radio_target, rule::parse_rule, table::parse_table_el, BabelCall,
CenterBlock, Clock, CommentBlock, Cookie, Drawer, DynBlock, Element, ExampleBlock, ExportBlock,
FnDef, FnRef, InlineCall, InlineSrc, Keyword, Link, List, ListItem, Macros, QuoteBlock,
Snippet, SourceBlock, SpecialBlock, Table, TableRow, Target, Timestamp, Title, VerseBlock,
};
pub trait ElementArena<'a> {
fn append_element<T: Into<Element<'a>>>(&mut self, element: T, parent: NodeId) -> NodeId;
@ -72,13 +75,8 @@ impl<'a> ElementArena<'a> for OwnedArena<'a, '_, '_> {
element: T,
parent: NodeId,
) -> NodeId {
if let Some(child) = self.arena[parent].last_child() {
let node = self.arena.new_node(element.into().into_owned());
child.insert_before(node, self.arena);
node
} else {
self.append_element(element, parent)
}
self.arena
.insert_before_last_child(element.into().into_owned(), parent)
}
}
@ -170,7 +168,7 @@ pub fn parse_section_and_headlines<'a, T: ElementArena<'a>>(
let mut last_end = 0;
for i in memchr_iter(b'\n', content.as_bytes()).chain(once(content.len())) {
if let Ok((mut tail, (headline_content, level))) = parse_headline(&content[last_end..]) {
if let Some((mut tail, (headline_content, level))) = parse_headline(&content[last_end..]) {
if last_end != 0 {
let node = arena.append_element(Element::Section, parent);
let content = &content[0..last_end];
@ -183,7 +181,7 @@ pub fn parse_section_and_headlines<'a, T: ElementArena<'a>>(
node,
});
while let Ok((new_tail, (content, level))) = parse_headline(tail) {
while let Some((new_tail, (content, level))) = parse_headline(tail) {
debug_assert_ne!(tail, new_tail);
let node = arena.append_element(Element::Headline { level }, parent);
containers.push(Container::Headline { content, node });
@ -266,7 +264,7 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
parent: NodeId,
containers: &mut Vec<Container<'a>>,
) -> Option<&'a str> {
if let Ok((tail, (fn_def, content))) = FnDef::parse(contents) {
if let Some((tail, (fn_def, content))) = FnDef::parse(contents) {
let node = arena.append_element(fn_def, parent);
containers.push(Container::Block { content, node });
return Some(tail);
@ -285,42 +283,37 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
match contents.as_bytes().get(0)? {
b'C' => {
if let Ok((tail, clock)) = Clock::parse(contents) {
arena.append_element(clock, parent);
return Some(tail);
}
let (tail, clock) = Clock::parse(contents)?;
arena.append_element(clock, parent);
Some(tail)
}
b'\'' => {
// TODO: LaTeX environment
None
}
b'-' => {
if let Ok((tail, _)) = parse_rule(contents) {
arena.append_element(Element::Rule, parent);
return Some(tail);
}
let tail = parse_rule(contents)?;
arena.append_element(Element::Rule, parent);
Some(tail)
}
b':' => {
if let Ok((tail, (drawer, content))) = Drawer::parse(contents) {
if let Some((tail, (drawer, content))) = Drawer::parse(contents) {
let node = arena.append_element(drawer, parent);
containers.push(Container::Block { content, node });
return Some(tail);
} else if let Ok((tail, value)) = parse_fixed_width(contents) {
arena.append_element(
Element::FixedWidth {
value: value.into(),
},
parent,
);
return Some(tail);
Some(tail)
} else {
let (tail, value) = parse_fixed_width(contents)?;
let value = value.into();
arena.append_element(Element::FixedWidth { value }, parent);
Some(tail)
}
}
b'|' => {
if let Some(tail) = parse_table(arena, contents, containers, parent) {
return Some(tail);
}
let tail = parse_table(arena, contents, containers, parent)?;
Some(tail)
}
b'#' => {
if let Ok((tail, (name, args, content))) = parse_block_element(contents) {
if let Some((tail, (name, args, content))) = parse_block_element(contents) {
match_block(
arena,
parent,
@ -329,12 +322,12 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
args.map(Into::into),
content,
);
return Some(tail);
} else if let Ok((tail, (dyn_block, content))) = DynBlock::parse(contents) {
Some(tail)
} else if let Some((tail, (dyn_block, content))) = DynBlock::parse(contents) {
let node = arena.append_element(dyn_block, parent);
containers.push(Container::Block { content, node });
return Some(tail);
} else if let Ok((tail, (key, optional, value))) = parse_keyword(contents) {
Some(tail)
} else if let Some((tail, (key, optional, value))) = parse_keyword(contents) {
if (&*key).eq_ignore_ascii_case("CALL") {
arena.append_element(
BabelCall {
@ -352,21 +345,16 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
parent,
);
}
return Some(tail);
} else if let Ok((tail, value)) = parse_comment(contents) {
arena.append_element(
Element::Comment {
value: value.into(),
},
parent,
);
return Some(tail);
Some(tail)
} else {
let (tail, value) = parse_comment(contents)?;
let value = value.into();
arena.append_element(Element::Comment { value }, parent);
Some(tail)
}
}
_ => (),
_ => None,
}
None
}
pub fn match_block<'a, T: ElementArena<'a>>(
@ -542,116 +530,97 @@ pub fn parse_inline<'a, T: ElementArena<'a>>(
return None;
}
let bytes = contents.as_bytes();
match bytes[0] {
match contents.as_bytes()[0] {
b'@' => {
if let Ok((tail, snippet)) = Snippet::parse(contents) {
arena.append_element(snippet, parent);
return Some(tail);
}
let (tail, snippet) = Snippet::parse(contents)?;
arena.append_element(snippet, parent);
Some(tail)
}
b'{' => {
if let Ok((tail, macros)) = Macros::parse(contents) {
arena.append_element(macros, parent);
return Some(tail);
}
let (tail, macros) = Macros::parse(contents)?;
arena.append_element(macros, parent);
Some(tail)
}
b'<' => {
if let Ok((tail, _content)) = parse_radio_target(contents) {
if let Some((tail, _content)) = parse_radio_target(contents) {
arena.append_element(Element::RadioTarget, parent);
return Some(tail);
} else if let Ok((tail, target)) = Target::parse(contents) {
Some(tail)
} else if let Some((tail, target)) = Target::parse(contents) {
arena.append_element(target, parent);
return Some(tail);
} else if let Ok((tail, timestamp)) = Timestamp::parse_active(contents) {
Some(tail)
} else if let Some((tail, timestamp)) = Timestamp::parse_active(contents) {
arena.append_element(timestamp, parent);
return Some(tail);
} else if let Ok((tail, timestamp)) = Timestamp::parse_diary(contents) {
Some(tail)
} else {
let (tail, timestamp) = Timestamp::parse_diary(contents)?;
arena.append_element(timestamp, parent);
return Some(tail);
Some(tail)
}
}
b'[' => {
if let Ok((tail, fn_ref)) = FnRef::parse(contents) {
if let Some((tail, fn_ref)) = FnRef::parse(contents) {
arena.append_element(fn_ref, parent);
return Some(tail);
} else if let Ok((tail, link)) = Link::parse(contents) {
Some(tail)
} else if let Some((tail, link)) = Link::parse(contents) {
arena.append_element(link, parent);
return Some(tail);
} else if let Ok((tail, cookie)) = Cookie::parse(contents) {
Some(tail)
} else if let Some((tail, cookie)) = Cookie::parse(contents) {
arena.append_element(cookie, parent);
return Some(tail);
} else if let Ok((tail, timestamp)) = Timestamp::parse_inactive(contents) {
Some(tail)
} else {
let (tail, timestamp) = Timestamp::parse_inactive(contents)?;
arena.append_element(timestamp, parent);
return Some(tail);
Some(tail)
}
}
b'*' => {
if let Some((tail, content)) = parse_emphasis(contents, b'*') {
let node = arena.append_element(Element::Bold, parent);
containers.push(Container::Inline { content, node });
return Some(tail);
}
let (tail, content) = parse_emphasis(contents, b'*')?;
let node = arena.append_element(Element::Bold, parent);
containers.push(Container::Inline { content, node });
Some(tail)
}
b'+' => {
if let Some((tail, content)) = parse_emphasis(contents, b'+') {
let node = arena.append_element(Element::Strike, parent);
containers.push(Container::Inline { content, node });
return Some(tail);
}
let (tail, content) = parse_emphasis(contents, b'+')?;
let node = arena.append_element(Element::Strike, parent);
containers.push(Container::Inline { content, node });
Some(tail)
}
b'/' => {
if let Some((tail, content)) = parse_emphasis(contents, b'/') {
let node = arena.append_element(Element::Italic, parent);
containers.push(Container::Inline { content, node });
return Some(tail);
}
let (tail, content) = parse_emphasis(contents, b'/')?;
let node = arena.append_element(Element::Italic, parent);
containers.push(Container::Inline { content, node });
Some(tail)
}
b'_' => {
if let Some((tail, content)) = parse_emphasis(contents, b'_') {
let node = arena.append_element(Element::Underline, parent);
containers.push(Container::Inline { content, node });
return Some(tail);
}
let (tail, content) = parse_emphasis(contents, b'_')?;
let node = arena.append_element(Element::Underline, parent);
containers.push(Container::Inline { content, node });
Some(tail)
}
b'=' => {
if let Some((tail, value)) = parse_emphasis(contents, b'=') {
arena.append_element(
Element::Verbatim {
value: value.into(),
},
parent,
);
return Some(tail);
}
let (tail, value) = parse_emphasis(contents, b'=')?;
let value = value.into();
arena.append_element(Element::Verbatim { value }, parent);
Some(tail)
}
b'~' => {
if let Some((tail, value)) = parse_emphasis(contents, b'~') {
arena.append_element(
Element::Code {
value: value.into(),
},
parent,
);
return Some(tail);
}
let (tail, value) = parse_emphasis(contents, b'~')?;
let value = value.into();
arena.append_element(Element::Code { value }, parent);
Some(tail)
}
b's' => {
if let Ok((tail, inline_src)) = InlineSrc::parse(contents) {
arena.append_element(inline_src, parent);
return Some(tail);
}
let (tail, inline_src) = InlineSrc::parse(contents)?;
arena.append_element(inline_src, parent);
Some(tail)
}
b'c' => {
if let Ok((tail, inline_call)) = InlineCall::parse(contents) {
arena.append_element(inline_call, parent);
return Some(tail);
}
let (tail, inline_call) = InlineCall::parse(contents)?;
arena.append_element(inline_call, parent);
Some(tail)
}
_ => (),
_ => None,
}
None
}
pub fn parse_list_items<'a, T: ElementArena<'a>>(
@ -701,20 +670,16 @@ pub fn parse_table<'a, T: ElementArena<'a>>(
}
Some("")
} else if let Ok((tail, value)) = parse_table_el(contents) {
arena.append_element(
Table::TableEl {
value: value.into(),
},
parent,
);
Some(tail)
} else {
None
let (tail, value) = parse_table_el(contents)?;
let value = value.into();
arena.append_element(Table::TableEl { value }, parent);
Some(tail)
}
}
pub fn line(input: &str) -> IResult<&str, &str> {
pub fn line<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
if let Some(i) = memchr(b'\n', input.as_bytes()) {
if i > 0 && input.as_bytes()[i - 1] == b'\r' {
Ok((&input[i + 1..], &input[0..i - 1]))
@ -726,73 +691,76 @@ pub fn line(input: &str) -> IResult<&str, &str> {
}
}
pub fn eol(input: &str) -> IResult<&str, &str> {
pub fn eol<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
verify(line, |s: &str| s.trim().is_empty())(input)
}
pub fn take_lines_while(predicate: impl Fn(&str) -> bool) -> impl Fn(&str) -> IResult<&str, &str> {
pub fn take_lines_while(predicate: impl Fn(&str) -> bool) -> impl Fn(&str) -> (&str, &str) {
move |input| {
let mut last_end = 0;
for i in memchr_iter(b'\n', input.as_bytes()) {
if i > 0 && input.as_bytes()[i - 1] == b'\r' {
if !predicate(&input[last_end..i - 1]) {
return Ok((&input[last_end..], &input[0..last_end]));
return (&input[last_end..], &input[0..last_end]);
}
} else if !predicate(&input[last_end..i]) {
return Ok((&input[last_end..], &input[0..last_end]));
return (&input[last_end..], &input[0..last_end]);
}
last_end = i + 1;
}
if !predicate(&input[last_end..]) {
Ok((&input[last_end..], &input[0..last_end]))
(&input[last_end..], &input[0..last_end])
} else {
Ok(("", input))
("", input)
}
}
}
pub fn skip_empty_lines(input: &str) -> &str {
take_lines_while(|line| line.trim().is_empty())(input)
.map(|(tail, _)| tail)
.unwrap_or(input)
take_lines_while(|line| line.trim().is_empty())(input).0
}
pub fn parse_headline(input: &str) -> IResult<&str, (&str, usize)> {
pub fn parse_headline(input: &str) -> Option<(&str, (&str, usize))> {
let (input_, level) = parse_headline_level(input)?;
let (input_, content) = take_lines_while(move |line| {
if let Ok((_, l)) = parse_headline_level(line) {
l > level
} else {
true
}
})(input_)?;
Ok((input_, (&input[0..level + content.len()], level)))
parse_headline_level(line)
.map(|(_, l)| l > level)
.unwrap_or(true)
})(input_);
Some((input_, (&input[0..level + content.len()], level)))
}
pub fn parse_headline_level(input: &str) -> IResult<&str, usize> {
let (input, stars) = take_while1(|c: char| c == '*')(input)?;
pub fn parse_headline_level(input: &str) -> Option<(&str, usize)> {
let (input, stars) = take_while1::<_, _, ()>(|c: char| c == '*')(input).ok()?;
if input.starts_with(' ') || input.starts_with('\n') || input.is_empty() {
Ok((input, stars.len()))
Some((input, stars.len()))
} else {
Err(Err::Error(error_position!(input, ErrorKind::Tag)))
None
}
}
pub fn parse_fixed_width(input: &str) -> IResult<&str, &str> {
verify(
take_lines_while(|line| line == ":" || line.starts_with(": ")),
|s: &str| !s.is_empty(),
)(input)
pub fn parse_fixed_width(input: &str) -> Option<(&str, &str)> {
let (input, content) = take_lines_while(|line| line == ":" || line.starts_with(": "))(input);
if !content.is_empty() {
Some((input, content))
} else {
None
}
}
pub fn parse_comment(input: &str) -> IResult<&str, &str> {
verify(
take_lines_while(|line| line == "#" || line.starts_with("# ")),
|s: &str| !s.is_empty(),
)(input)
pub fn parse_comment(input: &str) -> Option<(&str, &str)> {
let (input, content) = take_lines_while(|line| line == "#" || line.starts_with("# "))(input);
if !content.is_empty() {
Some((input, content))
} else {
None
}
}
pub fn take_one_word(input: &str) -> IResult<&str, &str> {
pub fn take_one_word<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
take_while1(|c: char| !c.is_ascii_whitespace())(input)
}