use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::anychar; use nom::combinator::not; use nom::combinator::opt; use nom::combinator::recognize; use nom::combinator::verify; use nom::multi::many1; use nom::multi::many_till; use nom::sequence::preceded; use nom::sequence::tuple; use super::Context; use crate::error::CustomError; use crate::error::Res; use crate::parser::exiting::ExitClass; use crate::parser::object::CitationReference; use crate::parser::object_parser::minimal_set_object; use crate::parser::parser_context::CitationBracket; use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_with_context::parser_with_context; use crate::parser::util::exit_matcher_parser; use crate::parser::util::get_consumed; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::parser::Object; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub fn citation_reference<'r, 's>( context: Context<'r, 's>, input: &'s str, ) -> Res<&'s str, CitationReference<'s>> { let (remaining, _prefix) = opt(parser_with_context!(key_prefix)(context))(input)?; let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?; let (remaining, _suffix) = opt(parser_with_context!(key_suffix)(context))(remaining)?; let source = get_consumed(input, remaining); Ok((remaining, CitationReference { source })) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub fn citation_reference_key<'r, 's>( context: Context<'r, 's>, input: &'s str, ) -> Res<&'s str, &'s str> { let (remaining, source) = recognize(tuple(( tag("@"), many1(verify( preceded( not(parser_with_context!(exit_matcher_parser)(context)), anychar, ), |c| { WORD_CONSTITUENT_CHARACTERS.contains(*c) || "-.:?~`'/*@+|(){}<>&_^$#%~".contains(*c) }, )), )))(input)?; Ok((remaining, source)) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn key_prefix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec>> { // TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient. let parser_context = context .with_additional_node(ContextElement::CitationBracket(CitationBracket { position: input, depth: 0, })) .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Beta, exit_matcher: &key_prefix_end, })); let (remaining, (children, _exit_contents)) = verify( many_till( parser_with_context!(minimal_set_object)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context), ), |(children, _exit_contents)| !children.is_empty(), )(input)?; Ok((remaining, children)) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn key_suffix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec>> { // TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient. let parser_context = context .with_additional_node(ContextElement::CitationBracket(CitationBracket { position: input, depth: 0, })) .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Beta, exit_matcher: &key_suffix_end, })); let (remaining, (children, _exit_contents)) = verify( many_till( parser_with_context!(minimal_set_object)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context), ), |(children, _exit_contents)| !children.is_empty(), )(input)?; Ok((remaining, children)) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r CitationBracket<'s>> { for node in context.iter() { match node.get_data() { ContextElement::CitationBracket(depth) => return Some(depth), _ => {} } } None } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn key_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { let context_depth = get_bracket_depth(context) .expect("This function should only be called from inside a citation reference."); let text_since_context_entry = get_consumed(context_depth.position, input); let mut current_depth = context_depth.depth; for c in text_since_context_entry.chars() { match c { '[' => { current_depth += 1; } ']' if current_depth == 0 => { panic!("Exceeded citation reference key prefix bracket depth.") } ']' if current_depth > 0 => { current_depth -= 1; } _ => {} } } if current_depth == 0 { let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input); if close_bracket.is_ok() { return close_bracket; } } alt(( tag(";"), recognize(parser_with_context!(citation_reference_key)(context)), ))(input) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn key_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { let context_depth = get_bracket_depth(context) .expect("This function should only be called from inside a citation reference."); let text_since_context_entry = get_consumed(context_depth.position, input); let mut current_depth = context_depth.depth; for c in text_since_context_entry.chars() { match c { '[' => { current_depth += 1; } ']' if current_depth == 0 => { panic!("Exceeded citation reference key prefix bracket depth.") } ']' if current_depth > 0 => { current_depth -= 1; } _ => {} } } if current_depth == 0 { let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input); if close_bracket.is_ok() { return close_bracket; } } tag(";")(input) }