diff --git a/org_mode_samples/object/citation/balanced_brackets.org b/org_mode_samples/object/citation/balanced_brackets.org new file mode 100644 index 0000000..08036b7 --- /dev/null +++ b/org_mode_samples/object/citation/balanced_brackets.org @@ -0,0 +1,22 @@ +# Extra open +[cite/a/b-_/foo:unbalancedglobal[prefix;keyprefix @foo keysuffix;globalsuffix] + +[cite/a/b-_/foo:globalprefix;unbalancedkey[prefix @foo keysuffix;globalsuffix] + +[cite/a/b-_/foo:globalprefix;keyprefix @foo unbalancedkey[suffix;globalsuffix] + +[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;unbalancedglobal[suffix] + + +# Extra close +[cite/a/b-_/foo:unbalancedglobal]prefix;keyprefix @foo keysuffix;globalsuffix] + +[cite/a/b-_/foo:globalprefix;unbalancedkey]prefix @foo keysuffix;globalsuffix] + +[cite/a/b-_/foo:globalprefix;keyprefix @foo unbalancedkey]suffix;globalsuffix] + +[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;unbalancedglobal]suffix] + + +# balanced: +[cite/a/b-_/foo:gl[obalpref]ix;ke[ypref]ix @foo ke[ysuff]ix;gl[obalsuff]ix] diff --git a/src/parser/citation.rs b/src/parser/citation.rs index ffce49a..94a6926 100644 --- a/src/parser/citation.rs +++ b/src/parser/citation.rs @@ -11,17 +11,16 @@ use nom::multi::many_till; use nom::multi::separated_list1; use nom::sequence::tuple; +use super::citation_reference::must_balance_bracket; use super::org_source::OrgSource; use super::Context; use crate::error::CustomError; use crate::error::Res; use crate::parser::citation_reference::citation_reference; use crate::parser::citation_reference::citation_reference_key; -use crate::parser::citation_reference::get_bracket_depth; use crate::parser::exiting::ExitClass; use crate::parser::object::Citation; use crate::parser::object_parser::standard_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; @@ -38,13 +37,15 @@ pub fn citation<'r, 's>( let (remaining, _) = tag_no_case("[cite")(input)?; let (remaining, _) = opt(citestyle)(remaining)?; let (remaining, _) = tag(":")(remaining)?; - let (remaining, _prefix) = opt(parser_with_context!(global_prefix)(context))(remaining)?; + let (remaining, _prefix) = + must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?; + let (remaining, _references) = separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?; - let (remaining, _suffix) = opt(tuple(( + let (remaining, _suffix) = must_balance_bracket(opt(tuple(( tag(";"), parser_with_context!(global_suffix)(context), - )))(remaining)?; + ))))(remaining)?; let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = space0(remaining)?; let source = get_consumed(input, remaining); @@ -83,15 +84,11 @@ fn global_prefix<'r, 's>( context: Context<'r, 's>, input: OrgSource<'s>, ) -> Res, 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 { + let exit_with_depth = global_prefix_end(input.get_bracket_depth()); + let parser_context = + context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, - exit_matcher: &global_prefix_end, + exit_matcher: &exit_with_depth, })); let (remaining, (children, _exit_contents)) = verify( many_till( @@ -104,28 +101,24 @@ fn global_prefix<'r, 's>( Ok((remaining, children)) } +fn global_prefix_end( + starting_bracket_depth: isize, +) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res, OrgSource<'s>> { + move |context: Context, input: OrgSource<'_>| { + _global_prefix_end(context, input, starting_bracket_depth) + } +} + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn global_prefix_end<'r, 's>( +fn _global_prefix_end<'r, 's>( context: Context<'r, 's>, input: OrgSource<'s>, + starting_bracket_depth: isize, ) -> Res, OrgSource<'s>> { - let context_depth = get_bracket_depth(context) - .expect("This function should only be called from inside a citation."); - let text_since_context_entry = get_consumed(context_depth.position, input); - let mut current_depth = context_depth.depth; - for c in Into::<&str>::into(text_since_context_entry).chars() { - match c { - '[' => { - current_depth += 1; - } - ']' if current_depth == 0 => { - panic!("Exceeded citation global prefix bracket depth.") - } - ']' if current_depth > 0 => { - current_depth -= 1; - } - _ => {} - } + let current_depth = input.get_bracket_depth() - starting_bracket_depth; + if current_depth < 0 { + // This shouldn't be possible because if depth is 0 then a closing bracket should end the citation. + unreachable!("Exceeded citation global prefix bracket depth.") } if current_depth == 0 { let close_bracket = tag::<&str, OrgSource<'_>, CustomError>>("]")(input); @@ -144,15 +137,11 @@ fn global_suffix<'r, 's>( context: Context<'r, 's>, input: OrgSource<'s>, ) -> Res, 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 { + let exit_with_depth = global_suffix_end(input.get_bracket_depth()); + let parser_context = + context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, - exit_matcher: &global_suffix_end, + exit_matcher: &exit_with_depth, })); let (remaining, (children, _exit_contents)) = verify( many_till( @@ -164,28 +153,24 @@ fn global_suffix<'r, 's>( Ok((remaining, children)) } +fn global_suffix_end( + starting_bracket_depth: isize, +) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res, OrgSource<'s>> { + move |context: Context, input: OrgSource<'_>| { + _global_suffix_end(context, input, starting_bracket_depth) + } +} + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn global_suffix_end<'r, 's>( +fn _global_suffix_end<'r, 's>( context: Context<'r, 's>, input: OrgSource<'s>, + starting_bracket_depth: isize, ) -> Res, OrgSource<'s>> { - let context_depth = get_bracket_depth(context) - .expect("This function should only be called from inside a citation."); - let text_since_context_entry = get_consumed(context_depth.position, input); - let mut current_depth = context_depth.depth; - for c in Into::<&str>::into(text_since_context_entry).chars() { - match c { - '[' => { - current_depth += 1; - } - ']' if current_depth == 0 => { - panic!("Exceeded citation global suffix bracket depth.") - } - ']' if current_depth > 0 => { - current_depth -= 1; - } - _ => {} - } + let current_depth = input.get_bracket_depth() - starting_bracket_depth; + if current_depth < 0 { + // This shouldn't be possible because if depth is 0 then a closing bracket should end the citation. + unreachable!("Exceeded citation global suffix bracket depth.") } if current_depth == 0 { let close_bracket = tag::<&str, OrgSource<'_>, CustomError>>("]")(input); diff --git a/src/parser/citation_reference.rs b/src/parser/citation_reference.rs index b854f6f..591d6dd 100644 --- a/src/parser/citation_reference.rs +++ b/src/parser/citation_reference.rs @@ -13,11 +13,11 @@ use nom::sequence::tuple; use super::org_source::OrgSource; use super::Context; use crate::error::CustomError; +use crate::error::MyError; 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; @@ -31,9 +31,11 @@ pub fn citation_reference<'r, 's>( context: Context<'r, 's>, input: OrgSource<'s>, ) -> Res, CitationReference<'s>> { - let (remaining, _prefix) = opt(parser_with_context!(key_prefix)(context))(input)?; + let (remaining, _prefix) = + must_balance_bracket(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 (remaining, _suffix) = + must_balance_bracket(opt(parser_with_context!(key_suffix)(context)))(remaining)?; let source = get_consumed(input, remaining); Ok(( @@ -69,15 +71,11 @@ fn key_prefix<'r, 's>( context: Context<'r, 's>, input: OrgSource<'s>, ) -> Res, 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 { + let exit_with_depth = key_prefix_end(input.get_bracket_depth()); + let parser_context = + context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, - exit_matcher: &key_prefix_end, + exit_matcher: &exit_with_depth, })); let (remaining, (children, _exit_contents)) = verify( many_till( @@ -94,15 +92,11 @@ fn key_suffix<'r, 's>( context: Context<'r, 's>, input: OrgSource<'s>, ) -> Res, 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 { + let exit_with_depth = key_suffix_end(input.get_bracket_depth()); + let parser_context = + context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, - exit_matcher: &key_suffix_end, + exit_matcher: &exit_with_depth, })); let (remaining, (children, _exit_contents)) = verify( many_till( @@ -114,39 +108,24 @@ fn key_suffix<'r, 's>( 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), - _ => {} - } +fn key_prefix_end( + starting_bracket_depth: isize, +) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res, OrgSource<'s>> { + move |context: Context, input: OrgSource<'_>| { + _key_prefix_end(context, input, starting_bracket_depth) } - None } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn key_prefix_end<'r, 's>( +fn _key_prefix_end<'r, 's>( context: Context<'r, 's>, input: OrgSource<'s>, + starting_bracket_depth: isize, ) -> Res, OrgSource<'s>> { - 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 Into::<&str>::into(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; - } - _ => {} - } + let current_depth = input.get_bracket_depth() - starting_bracket_depth; + if current_depth < 0 { + // This shouldn't be possible because if depth is 0 then a closing bracket should end the citation. + unreachable!("Exceeded citation key prefix bracket depth.") } if current_depth == 0 { let close_bracket = tag::<&str, OrgSource<'_>, CustomError>>("]")(input); @@ -160,28 +139,24 @@ fn key_prefix_end<'r, 's>( ))(input) } +fn key_suffix_end( + starting_bracket_depth: isize, +) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res, OrgSource<'s>> { + move |context: Context, input: OrgSource<'_>| { + _key_suffix_end(context, input, starting_bracket_depth) + } +} + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn key_suffix_end<'r, 's>( - context: Context<'r, 's>, +fn _key_suffix_end<'r, 's>( + _context: Context<'r, 's>, input: OrgSource<'s>, + starting_bracket_depth: isize, ) -> Res, OrgSource<'s>> { - 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 Into::<&str>::into(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; - } - _ => {} - } + let current_depth = input.get_bracket_depth() - starting_bracket_depth; + if current_depth < 0 { + // This shouldn't be possible because if depth is 0 then a closing bracket should end the citation. + unreachable!("Exceeded citation key suffix bracket depth.") } if current_depth == 0 { let close_bracket = tag::<&str, OrgSource<'_>, CustomError>>("]")(input); @@ -191,3 +166,21 @@ fn key_suffix_end<'r, 's>( } tag(";")(input) } + +pub fn must_balance_bracket<'s, F, O>( + mut inner: F, +) -> impl FnMut(OrgSource<'s>) -> Res, O> +where + F: FnMut(OrgSource<'s>) -> Res, O>, +{ + move |input: OrgSource<'_>| { + let pre_bracket_depth = input.get_bracket_depth(); + let (remaining, output) = inner(input)?; + if remaining.get_bracket_depth() - pre_bracket_depth != 0 { + return Err(nom::Err::Error(CustomError::MyError(MyError( + "UnbalancedBrackets".into(), + )))); + } + Ok((remaining, output)) + } +} diff --git a/src/parser/parser_context.rs b/src/parser/parser_context.rs index 034d864..f3fc170 100644 --- a/src/parser/parser_context.rs +++ b/src/parser/parser_context.rs @@ -124,21 +124,6 @@ pub enum ContextElement<'r, 's> { /// radio links matching the contents of radio targets. RadioTarget(Vec<&'r Vec>>), - /// Stores the current bracket depth inside a citation. - /// - /// The global prefix, global suffix, key prefix, and key suffix - /// inside a footnote reference must have balanced brackets [] - /// inside the definition, so this stores the amount of opening - /// brackets subtracted by the amount of closing brackets within - /// the definition must equal zero. None of the prefixes or - /// suffixes can be nested inside each other so we can use a - /// single type for this without conflict. - /// - /// A reference to the position in the string is also included so - /// unbalanced brackets can be detected in the middle of an - /// object. - CitationBracket(CitationBracket<'s>), - /// Stores the current bracket or parenthesis depth inside an inline babel call. /// /// Inside an inline babel call the headers must have balanced @@ -183,12 +168,6 @@ pub struct ExitMatcherNode<'r> { pub class: ExitClass, } -#[derive(Debug)] -pub struct CitationBracket<'s> { - pub position: OrgSource<'s>, - pub depth: usize, -} - #[derive(Debug)] pub struct BabelHeaderBracket<'s> { pub position: OrgSource<'s>,