diff --git a/src/context/context.rs b/src/context/context.rs index 0f41963b..0baa2e8b 100644 --- a/src/context/context.rs +++ b/src/context/context.rs @@ -21,6 +21,9 @@ pub enum ContextElement<'r, 's> { /// Stores the name of the current element to prevent directly nesting elements of the same type. Context(&'r str), + /// Stores the name of the current object to prevent directly nesting elements of the same type. + ContextObject(&'r str), + /// Indicates if elements should consume the whitespace after them. ConsumeTrailingWhitespace(bool), diff --git a/src/parser/text_markup.rs b/src/parser/text_markup.rs index 922d3f6a..d1313709 100644 --- a/src/parser/text_markup.rs +++ b/src/parser/text_markup.rs @@ -18,6 +18,7 @@ use tracing::span; use super::object_parser::standard_set_object; use super::org_source::OrgSource; use super::radio_link::RematchObject; +use super::util::in_object_section; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use crate::context::parser_with_context; use crate::context::ContextElement; @@ -177,16 +178,26 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>( input: OrgSource<'s>, marker_symbol: &'c str, ) -> Res, Vec>> { + if in_object_section(context, marker_symbol) { + return Err(nom::Err::Error(CustomError::MyError(MyError( + "Cannot nest objects of the same type".into(), + )))); + } + let (remaining, _) = pre(context, input)?; let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, _peek_not_whitespace) = peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?; let text_markup_end_specialized = text_markup_end(open.into()); - let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { - class: ExitClass::Gamma, - exit_matcher: &text_markup_end_specialized, - }); - let parser_context = context.with_additional_node(&parser_context); + let contexts = [ + ContextElement::ContextObject(marker_symbol), + ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Gamma, + exit_matcher: &text_markup_end_specialized, + }), + ]; + let parser_context = context.with_additional_node(&contexts[0]); + let parser_context = parser_context.with_additional_node(&contexts[1]); let (remaining, (children, _exit_contents)) = verify( many_till( @@ -230,16 +241,25 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>( input: OrgSource<'s>, marker_symbol: &'c str, ) -> Res, OrgSource<'s>> { + if in_object_section(context, marker_symbol) { + return Err(nom::Err::Error(CustomError::MyError(MyError( + "Cannot nest objects of the same type".into(), + )))); + } let (remaining, _) = pre(context, input)?; let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, _peek_not_whitespace) = peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?; let text_markup_end_specialized = text_markup_end(open.into()); - let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { - class: ExitClass::Gamma, - exit_matcher: &text_markup_end_specialized, - }); - let parser_context = context.with_additional_node(&parser_context); + let contexts = [ + ContextElement::ContextObject(marker_symbol), + ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Gamma, + exit_matcher: &text_markup_end_specialized, + }), + ]; + let parser_context = context.with_additional_node(&contexts[0]); + let parser_context = parser_context.with_additional_node(&contexts[1]); let (remaining, contents) = recognize(verify( many_till( diff --git a/src/parser/util.rs b/src/parser/util.rs index 32576ad2..c715a505 100644 --- a/src/parser/util.rs +++ b/src/parser/util.rs @@ -24,7 +24,6 @@ pub const WORD_CONSTITUENT_CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; /// Check if we are below a section of the given section type regardless of depth -#[allow(dead_code)] pub fn in_section<'b, 'g, 'r, 's, 'x>( context: RefContext<'b, 'g, 'r, 's>, section_name: &'x str, @@ -53,6 +52,20 @@ pub fn immediate_in_section<'b, 'g, 'r, 's, 'x>( false } +/// Check if we are below a section of the given section type regardless of depth +pub fn in_object_section<'b, 'g, 'r, 's, 'x>( + context: RefContext<'b, 'g, 'r, 's>, + section_name: &'x str, +) -> bool { + for thing in context.iter() { + match thing { + ContextElement::ContextObject(name) if *name == section_name => return true, + _ => {} + } + } + false +} + /// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser. pub fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSource<'s> { input.get_until(remaining)