diff --git a/src/context/context.rs b/src/context/context.rs index 22c4798..def26bf 100644 --- a/src/context/context.rs +++ b/src/context/context.rs @@ -12,6 +12,7 @@ use crate::error::CustomError; use crate::error::MyError; use crate::error::Res; use crate::parser::OrgSource; +use crate::types::Keyword; #[derive(Debug)] pub(crate) enum ContextElement<'r, 's> { @@ -27,11 +28,22 @@ pub(crate) enum ContextElement<'r, 's> { /// Indicates if elements should consume the whitespace after them. ConsumeTrailingWhitespace(bool), + /// Indicate that we are parsing a paragraph that already has affiliated keywords. + /// + /// The value stored is the start of the element after the affiliated keywords. In this way, we can ensure that we do not exit an element immediately after the affiliated keyword had been consumed. + HasAffiliatedKeyword(HasAffiliatedKeywordInner<'r, 's>), + /// This is just here to use the 's lifetime until I'm sure we can eliminate it from ContextElement. #[allow(dead_code)] Placeholder(PhantomData<&'s str>), } +#[derive(Debug, Clone)] +pub(crate) struct HasAffiliatedKeywordInner<'r, 's> { + pub(crate) start_after_affiliated_keywords: OrgSource<'s>, + pub(crate) keywords: &'r Vec>, +} + pub(crate) struct ExitMatcherNode<'r> { // TODO: Should this be "&'r DynContextMatcher<'c>" ? pub(crate) exit_matcher: &'r DynContextMatcher<'r>, diff --git a/src/context/mod.rs b/src/context/mod.rs index 7e45fdd..27e6a6d 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -21,6 +21,7 @@ type DynMatcher<'c> = dyn Matcher + 'c; pub(crate) use context::Context; pub(crate) use context::ContextElement; pub(crate) use context::ExitMatcherNode; +pub(crate) use context::HasAffiliatedKeywordInner; pub(crate) use exiting::ExitClass; pub use file_access_interface::FileAccessInterface; pub use file_access_interface::LocalFileAccessInterface; diff --git a/src/parser/org_source.rs b/src/parser/org_source.rs index 1aa29a7..eb827a0 100644 --- a/src/parser/org_source.rs +++ b/src/parser/org_source.rs @@ -14,7 +14,7 @@ use crate::error::MyError; pub(crate) type BracketDepth = i16; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] pub(crate) struct OrgSource<'s> { full_source: &'s str, start: usize, diff --git a/src/parser/paragraph.rs b/src/parser/paragraph.rs index 82a8f6c..2bce4b5 100644 --- a/src/parser/paragraph.rs +++ b/src/parser/paragraph.rs @@ -12,12 +12,16 @@ use super::keyword::affiliated_keyword; use super::org_source::OrgSource; use super::util::blank_line; use super::util::get_consumed; +use super::util::get_has_affiliated_keyword; use super::util::get_name; use crate::context::parser_with_context; use crate::context::ContextElement; use crate::context::ExitClass; use crate::context::ExitMatcherNode; +use crate::context::HasAffiliatedKeywordInner; use crate::context::RefContext; +use crate::error::CustomError; +use crate::error::MyError; use crate::error::Res; use crate::parser::object_parser::standard_set_object; use crate::parser::util::exit_matcher_parser; @@ -30,12 +34,18 @@ pub(crate) fn paragraph<'b, 'g, 'r, 's>( input: OrgSource<'s>, ) -> Res, Paragraph<'s>> { let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?; - - let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { - class: ExitClass::Gamma, - exit_matcher: ¶graph_end, - }); - let parser_context = context.with_additional_node(&parser_context); + let contexts = [ + ContextElement::HasAffiliatedKeyword(HasAffiliatedKeywordInner { + start_after_affiliated_keywords: input, + keywords: &affiliated_keywords, + }), + ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Gamma, + exit_matcher: ¶graph_end, + }), + ]; + let parser_context = context.with_additional_node(&contexts[0]); + let parser_context = parser_context.with_additional_node(&contexts[1]); let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); @@ -63,10 +73,21 @@ fn paragraph_end<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { - let non_paragraph_element_matcher = parser_with_context!(detect_element(false))(context); + let regular_end = recognize(tuple((start_of_line, many1(blank_line))))(input); + if regular_end.is_ok() { + return regular_end; + } + match get_has_affiliated_keyword(context) { + Some(start_post_affiliated_keywords) if input == start_post_affiliated_keywords => { + return Err(nom::Err::Error(CustomError::MyError(MyError( + "No exit due to affiliated keywords.", + )))); + } + _ => {} + } + // Check to see if input is the start of a HasAffiliatedKeyword alt(( - recognize(tuple((start_of_line, many1(blank_line)))), - recognize(non_paragraph_element_matcher), + recognize(parser_with_context!(detect_element(false))(context)), eof, ))(input) } diff --git a/src/parser/util.rs b/src/parser/util.rs index a36c53a..02dcfc6 100644 --- a/src/parser/util.rs +++ b/src/parser/util.rs @@ -284,3 +284,21 @@ pub(crate) fn get_name<'s>(affiliated_keywords: &Vec>) -> Option<&'s .last(); name_keyword.map(|kw| kw.value) } + +pub(crate) fn get_has_affiliated_keyword<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, +) -> Option> { + for context in context.iter() { + match context { + ContextElement::HasAffiliatedKeyword(inner) => { + if !inner.keywords.is_empty() { + return Some(inner.start_after_affiliated_keywords); + } else { + return None; + } + } + _ => {} + } + } + None +}