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, 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>, Option>, Option>, Option>, ), > { 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>> { 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>> { 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, Option>> { 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>> { 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, Option>> { 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, Option>> { 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> { let input = OrgSource::new("()"); let (remaining, call) = opt(babel_call_call)(input)?; assert_eq!(Into::<&str>::into(remaining), "()"); assert_eq!(call, None); Ok(()) } }