244 lines
7.3 KiB
Rust
244 lines
7.3 KiB
Rust
use nom::branch::alt;
|
|
use nom::bytes::complete::tag;
|
|
use nom::bytes::complete::tag_no_case;
|
|
use nom::character::complete::anychar;
|
|
use nom::character::complete::one_of;
|
|
use nom::character::complete::space0;
|
|
use nom::combinator::consumed;
|
|
use nom::combinator::opt;
|
|
use nom::combinator::peek;
|
|
use nom::combinator::recognize;
|
|
use nom::combinator::verify;
|
|
use nom::multi::many0;
|
|
use nom::multi::many_till;
|
|
use nom::sequence::tuple;
|
|
use nom::InputTake;
|
|
|
|
use super::keyword::affiliated_keyword;
|
|
use super::org_source::BracketDepth;
|
|
use super::util::get_name;
|
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
|
use super::util::start_of_line;
|
|
use super::OrgSource;
|
|
use crate::context::Matcher;
|
|
use crate::context::RefContext;
|
|
use crate::error::CustomError;
|
|
use crate::error::MyError;
|
|
use crate::error::Res;
|
|
use crate::parser::util::get_consumed;
|
|
use crate::parser::util::org_line_ending;
|
|
use crate::types::BabelCall;
|
|
|
|
#[cfg_attr(
|
|
feature = "tracing",
|
|
tracing::instrument(ret, level = "debug", skip(context))
|
|
)]
|
|
pub(crate) fn babel_call<'b, 'g, 'r, 's>(
|
|
context: RefContext<'b, 'g, 'r, 's>,
|
|
input: OrgSource<'s>,
|
|
) -> Res<OrgSource<'s>, BabelCall<'s>> {
|
|
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
|
|
|
start_of_line(remaining)?;
|
|
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
|
|
|
|
if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) {
|
|
let (remaining, _trailing_ws) =
|
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
|
let source = get_consumed(input, remaining);
|
|
return Ok((
|
|
remaining,
|
|
BabelCall {
|
|
source: Into::<&str>::into(source),
|
|
name: get_name(&affiliated_keywords),
|
|
value: Into::<&str>::into(line_break.take(0)),
|
|
call: None,
|
|
inside_header: None,
|
|
arguments: None,
|
|
end_header: None,
|
|
},
|
|
));
|
|
}
|
|
|
|
let (remaining, _ws) = space0(remaining)?;
|
|
let (remaining, (value, (call, inside_header, arguments, end_header))) =
|
|
consumed(babel_call_value)(remaining)?;
|
|
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
|
|
|
|
let (remaining, _trailing_ws) =
|
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
|
let source = get_consumed(input, remaining);
|
|
|
|
Ok((
|
|
remaining,
|
|
BabelCall {
|
|
source: Into::<&str>::into(source),
|
|
name: get_name(&affiliated_keywords),
|
|
value: Into::<&str>::into(value).trim_end(),
|
|
call: call.map(Into::<&str>::into),
|
|
inside_header: inside_header.map(Into::<&str>::into),
|
|
arguments: arguments.map(Into::<&str>::into),
|
|
end_header: end_header.map(Into::<&str>::into),
|
|
},
|
|
))
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn babel_call_value<'s>(
|
|
input: OrgSource<'s>,
|
|
) -> Res<
|
|
OrgSource<'s>,
|
|
(
|
|
Option<OrgSource<'s>>,
|
|
Option<OrgSource<'s>>,
|
|
Option<OrgSource<'s>>,
|
|
Option<OrgSource<'s>>,
|
|
),
|
|
> {
|
|
let (remaining, call) = opt(babel_call_call)(input)?;
|
|
let (remaining, inside_header) = opt(inside_header)(remaining)?;
|
|
let (remaining, arguments) = opt(arguments)(remaining)?;
|
|
let (remaining, end_header) = opt(end_header)(remaining)?;
|
|
Ok((
|
|
remaining,
|
|
(call, inside_header, arguments.flatten(), end_header),
|
|
))
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn babel_call_call<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
verify(
|
|
recognize(many_till(
|
|
anychar,
|
|
alt((
|
|
peek(recognize(one_of("[("))),
|
|
recognize(tuple((space0, org_line_ending))),
|
|
)),
|
|
)),
|
|
|s| s.len() > 0,
|
|
)(input)
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn inside_header<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
let (remaining, contents) = balanced_bracket(
|
|
|i| tag("[")(i),
|
|
|i| peek(tag("]"))(i),
|
|
|i| recognize(tuple((space0, org_line_ending)))(i),
|
|
|i| tag("]")(i),
|
|
|s| s.get_bracket_depth(),
|
|
)(input)?;
|
|
let (contents_start, _) = tag("[")(input)?;
|
|
Ok((remaining, contents.unwrap_or(contents_start.take(0))))
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn arguments<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
|
|
balanced_bracket(
|
|
|i| tag("(")(i),
|
|
|i| peek(tag(")"))(i),
|
|
|i| recognize(tuple((space0, org_line_ending)))(i),
|
|
|i| tag(")")(i),
|
|
|s| s.get_parenthesis_depth(),
|
|
)(input)
|
|
}
|
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
fn end_header<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
let (remaining, _) = space0(input)?;
|
|
verify(
|
|
recognize(many_till(anychar, peek(tuple((space0, org_line_ending))))),
|
|
|s| s.len() > 0,
|
|
)(remaining)
|
|
}
|
|
|
|
fn balanced_bracket<
|
|
O: Matcher,
|
|
S: Matcher,
|
|
F: Matcher,
|
|
E: Matcher,
|
|
D: for<'ss> Fn(OrgSource<'ss>) -> BracketDepth,
|
|
>(
|
|
opening_parser: O,
|
|
stop_parser: S,
|
|
fail_parser: F,
|
|
end_parser: E,
|
|
depth_function: D,
|
|
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
|
|
move |input| {
|
|
impl_balanced_bracket::<&O, &S, &F, &E, &D>(
|
|
input,
|
|
&opening_parser,
|
|
&stop_parser,
|
|
&fail_parser,
|
|
&end_parser,
|
|
&depth_function,
|
|
)
|
|
}
|
|
}
|
|
|
|
fn impl_balanced_bracket<
|
|
's,
|
|
O: Matcher,
|
|
S: Matcher,
|
|
F: Matcher,
|
|
E: Matcher,
|
|
D: for<'ss> Fn(OrgSource<'ss>) -> BracketDepth,
|
|
>(
|
|
input: OrgSource<'s>,
|
|
opening_parser: O,
|
|
stop_parser: S,
|
|
fail_parser: F,
|
|
end_parser: E,
|
|
depth_function: D,
|
|
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
|
|
let (mut remaining, _) = opening_parser(input)?;
|
|
let contents_start = remaining;
|
|
let original_depth = depth_function(remaining);
|
|
|
|
loop {
|
|
let bracket_depth = depth_function(remaining);
|
|
if bracket_depth == original_depth {
|
|
let (remain, stop_result) = opt(&stop_parser)(remaining)?;
|
|
remaining = remain;
|
|
if stop_result.is_some() {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if fail_parser(remaining).is_ok() {
|
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
"Fail parser matched.",
|
|
))));
|
|
}
|
|
|
|
let (remain, _) = anychar(remaining)?;
|
|
remaining = remain;
|
|
}
|
|
let contents_end = remaining;
|
|
|
|
let (remaining, _) = end_parser(remaining)?;
|
|
let contents = if contents_start != contents_end {
|
|
Some(contents_start.get_until(contents_end))
|
|
} else {
|
|
None
|
|
};
|
|
Ok((remaining, contents))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use nom::combinator::opt;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn simple_call() -> Result<(), Box<dyn std::error::Error>> {
|
|
let input = OrgSource::new("()");
|
|
let (remaining, call) = opt(babel_call_call)(input)?;
|
|
assert_eq!(Into::<&str>::into(remaining), "()");
|
|
assert_eq!(call, None);
|
|
Ok(())
|
|
}
|
|
}
|