From 15e8d1ab77662a46912976c08ca45ae668744d94 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 3 Sep 2023 00:05:47 -0400 Subject: [PATCH] Implement check_exit_matcher. --- src/context/parser_context.rs | 53 +++++++++++++++ src/parser/document.rs | 74 ++++++++++++++------- src/parser/plain_list.rs | 117 ++++++++++++++++++++++------------ src/parser/target.rs | 19 ++++-- src/parser/text_markup.rs | 1 - 5 files changed, 191 insertions(+), 73 deletions(-) diff --git a/src/context/parser_context.rs b/src/context/parser_context.rs index 098f4da..e5cc87b 100644 --- a/src/context/parser_context.rs +++ b/src/context/parser_context.rs @@ -1,10 +1,13 @@ use nom::combinator::eof; +use nom::IResult; use super::exiting::ExitClass; use super::global_settings::GlobalSettings; use super::list::List; use super::DynContextMatcher; use super::RefContext; +use crate::error::CustomError; +use crate::error::MyError; use crate::error::Res; use crate::parser::OrgSource; use crate::types::Object; @@ -169,6 +172,56 @@ impl<'r, 's> Context<'r, 's> { tree: parent_tree.clone(), }) } + + pub fn get_data(&self) -> &ContextElement<'r, 's> { + self.tree.get_data() + } + + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] + pub fn check_exit_matcher( + &'r self, + i: OrgSource<'s>, + ) -> IResult, OrgSource<'s>, CustomError>> { + let mut current_class_filter = ExitClass::Gamma; + for current_node in self.iter_context() { + let context_element = current_node.get_data(); + match context_element { + ContextElement::ExitMatcherNode(exit_matcher) => { + if exit_matcher.class as u32 <= current_class_filter as u32 { + current_class_filter = exit_matcher.class; + let local_result = (exit_matcher.exit_matcher)(¤t_node, i); + if local_result.is_ok() { + return local_result; + } + } + } + _ => {} + }; + } + // TODO: Make this a specific error instead of just a generic MyError + return Err(nom::Err::Error(CustomError::MyError(MyError( + "NoExit".into(), + )))); + } + + /// Indicates if elements should consume the whitespace after them. + /// + /// Defaults to true. + pub fn should_consume_trailing_whitespace(&self) -> bool { + self._should_consume_trailing_whitespace().unwrap_or(true) + } + + fn _should_consume_trailing_whitespace(&self) -> Option { + for current_node in self.iter() { + match current_node { + ContextElement::ConsumeTrailingWhitespace(should) => { + return Some(*should); + } + _ => {} + } + } + None + } } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] diff --git a/src/parser/document.rs b/src/parser/document.rs index 68db3da..38dad44 100644 --- a/src/parser/document.rs +++ b/src/parser/document.rs @@ -17,8 +17,6 @@ use nom::multi::many_till; use nom::multi::separated_list1; use nom::sequence::tuple; -use super::element::Element; -use super::object::Object; use super::org_source::convert_error; use super::org_source::OrgSource; use super::token::AllTokensIterator; @@ -26,6 +24,14 @@ use super::token::Token; use super::util::exit_matcher_parser; use super::util::get_consumed; use super::util::start_of_line; +use crate::context::parser_with_context; +use crate::context::Context; +use crate::context::ContextElement; +use crate::context::ExitClass; +use crate::context::ExitMatcherNode; +use crate::context::GlobalSettings; +use crate::context::List; +use crate::context::RefContext; use crate::error::Res; use crate::parser::comment::comment; use crate::parser::element_parser::element; @@ -34,11 +40,19 @@ use crate::parser::planning::planning; use crate::parser::property_drawer::property_drawer; use crate::parser::util::blank_line; use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; +use crate::types::Document; +use crate::types::DocumentElement; +use crate::types::Element; +use crate::types::Heading; +use crate::types::Object; +use crate::types::Section; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[allow(dead_code)] pub fn document(input: &str) -> Res<&str, Document> { - let initial_context = Context::default(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let wrapped_input = OrgSource::new(input); let (remaining, document) = _document(&initial_context, wrapped_input) .map(|(rem, out)| (Into::<&str>::into(rem), out)) @@ -58,9 +72,9 @@ pub fn document(input: &str) -> Res<&str, Document> { .map(|rt| &rt.children) .collect(); if !all_radio_targets.is_empty() { - let initial_context = initial_context - .with_additional_node(ContextElement::RadioTarget(all_radio_targets)); - let (remaining, document) = _document(&initial_context, wrapped_input) + let parser_context = ContextElement::RadioTarget(all_radio_targets); + let parser_context = initial_context.with_additional_node(&parser_context); + let (remaining, document) = _document(&parser_context, wrapped_input) .map(|(rem, out)| (Into::<&str>::into(rem), out)) .map_err(convert_error)?; return Ok((remaining.into(), document)); @@ -96,15 +110,21 @@ fn zeroth_section<'r, 's>( input: OrgSource<'s>, ) -> Res, Section<'s>> { // TODO: The zeroth section is specialized so it probably needs its own parser - let parser_context = context - .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) - .with_additional_node(ContextElement::Context("section")) - .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { + let contexts = [ + ContextElement::ConsumeTrailingWhitespace(true), + ContextElement::Context("section"), + ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Document, exit_matcher: §ion_end, - })); + }), + ]; + let parser_context = context + .with_additional_node(&contexts[0]) + .with_additional_node(&contexts[1]) + .with_additional_node(&contexts[2]); + let without_consuming_whitespace_context = ContextElement::ConsumeTrailingWhitespace(false); let without_consuming_whitespace_context = - parser_context.with_additional_node(ContextElement::ConsumeTrailingWhitespace(false)); + parser_context.with_additional_node(&without_consuming_whitespace_context); let element_matcher = parser_with_context!(element(true))(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); @@ -150,13 +170,18 @@ fn section<'r, 's>( mut input: OrgSource<'s>, ) -> Res, Section<'s>> { // TODO: The zeroth section is specialized so it probably needs its own parser - let parser_context = context - .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) - .with_additional_node(ContextElement::Context("section")) - .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { + let contexts = [ + ContextElement::ConsumeTrailingWhitespace(true), + ContextElement::Context("section"), + ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Document, exit_matcher: §ion_end, - })); + }), + ]; + let parser_context = context + .with_additional_node(&contexts[0]) + .with_additional_node(&contexts[1]) + .with_additional_node(&contexts[2]); let element_matcher = parser_with_context!(element(true))(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let (mut remaining, (planning_element, property_drawer_element)) = tuple(( @@ -204,8 +229,9 @@ fn section_end<'r, 's>( const fn heading( parent_stars: usize, -) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res, Heading<'s>> { - move |context: Context, input: OrgSource<'_>| _heading(context, input, parent_stars) +) -> impl for<'b, 'r, 's> Fn(RefContext<'b, 'r, 's>, OrgSource<'s>) -> Res, Heading<'s>> +{ + move |context, input: OrgSource<'_>| _heading(context, input, parent_stars) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -270,11 +296,11 @@ fn headline<'r, 's>( Vec<&'s str>, ), > { - let parser_context = - context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { - class: ExitClass::Document, - exit_matcher: &headline_title_end, - })); + let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Document, + exit_matcher: &headline_title_end, + }); + let parser_context = context.with_additional_node(&parser_context); let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context); let ( diff --git a/src/parser/plain_list.rs b/src/parser/plain_list.rs index 0007853..8960ec0 100644 --- a/src/parser/plain_list.rs +++ b/src/parser/plain_list.rs @@ -16,21 +16,26 @@ use nom::multi::many1; use nom::multi::many_till; use nom::sequence::tuple; -use super::greater_element::PlainList; -use super::greater_element::PlainListItem; +use super::element_parser::element; +use super::object_parser::standard_set_object; use super::org_source::OrgSource; use super::util::non_whitespace_character; -use super::Context; -use super::Object; +use crate::context::parser_with_context; +use crate::context::ContextElement; +use crate::context::ExitClass; +use crate::context::ExitMatcherNode; +use crate::context::RefContext; use crate::error::CustomError; use crate::error::MyError; use crate::error::Res; -use crate::parser::element_parser::element; use crate::parser::util::blank_line; use crate::parser::util::exit_matcher_parser; use crate::parser::util::get_consumed; use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; use crate::parser::util::start_of_line; +use crate::types::Object; +use crate::types::PlainList; +use crate::types::PlainListItem; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res, ()> { @@ -59,13 +64,19 @@ pub fn plain_list<'r, 's>( context: RefContext<'_, 'r, 's>, input: OrgSource<'s>, ) -> Res, PlainList<'s>> { - let parser_context = context - .with_additional_node(ContextElement::Context("plain list")) - .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) - .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { + let contexts = [ + ContextElement::Context("plain list"), + ContextElement::ConsumeTrailingWhitespace(true), + ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Beta, exit_matcher: &plain_list_end, - })); + }), + ]; + + let parser_context = context + .with_additional_node(&contexts[0]) + .with_additional_node(&contexts[1]) + .with_additional_node(&contexts[2]); // children stores tuple of (input string, parsed object) so we can re-parse the final item let mut children = Vec::new(); let mut first_item_indentation: Option = None; @@ -107,8 +118,8 @@ pub fn plain_list<'r, 's>( )))); } }; - let final_item_context = - parser_context.with_additional_node(ContextElement::ConsumeTrailingWhitespace(false)); + let final_item_context = ContextElement::ConsumeTrailingWhitespace(false); + let final_item_context = parser_context.with_additional_node(&final_item_context); let (remaining, reparsed_final_item) = parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?; children.push((final_child_start, reparsed_final_item)); @@ -164,12 +175,16 @@ pub fn plain_list_item<'r, 's>( }; let (remaining, _ws) = item_tag_post_gap(context, remaining)?; let exit_matcher = plain_list_item_end(indent_level); - let parser_context = context - .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) - .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { + let contexts = [ + ContextElement::ConsumeTrailingWhitespace(true), + ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Beta, exit_matcher: &exit_matcher, - })); + }), + ]; + let parser_context = context + .with_additional_node(&contexts[0]) + .with_additional_node(&contexts[1]); let (mut remaining, (mut children, _exit_contents)) = many_till( include_input(parser_with_context!(element(true))(&parser_context)), @@ -177,8 +192,8 @@ pub fn plain_list_item<'r, 's>( )(remaining)?; if !children.is_empty() && !context.should_consume_trailing_whitespace() { - let final_item_context = - parser_context.with_additional_node(ContextElement::ConsumeTrailingWhitespace(false)); + let final_item_context = ContextElement::ConsumeTrailingWhitespace(false); + let final_item_context = parser_context.with_additional_node(&final_item_context); let (final_child_start, _original_final_child) = children .pop() .expect("if-statement already checked that children was non-empty."); @@ -249,9 +264,10 @@ fn plain_list_end<'r, 's>( const fn plain_list_item_end( indent_level: usize, -) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res, OrgSource<'s>> { +) -> impl for<'b, 'r, 's> Fn(RefContext<'b, 'r, 's>, OrgSource<'s>) -> Res, OrgSource<'s>> +{ let line_indented_lte_matcher = line_indented_lte(indent_level); - move |context: Context, input: OrgSource<'_>| { + move |context, input: OrgSource<'_>| { _plain_list_item_end(context, input, &line_indented_lte_matcher) } } @@ -263,8 +279,8 @@ const fn plain_list_item_end( fn _plain_list_item_end<'r, 's>( context: RefContext<'_, 'r, 's>, input: OrgSource<'s>, - line_indented_lte_matcher: impl for<'rr, 'ss> Fn( - Context<'rr, 'ss>, + line_indented_lte_matcher: impl for<'bb, 'rr, 'ss> Fn( + RefContext<'bb, 'rr, 'ss>, OrgSource<'ss>, ) -> Res, OrgSource<'ss>>, ) -> Res, OrgSource<'s>> { @@ -277,8 +293,9 @@ fn _plain_list_item_end<'r, 's>( const fn line_indented_lte( indent_level: usize, -) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res, OrgSource<'s>> { - move |context: Context, input: OrgSource<'_>| _line_indented_lte(context, input, indent_level) +) -> impl for<'b, 'r, 's> Fn(RefContext<'b, 'r, 's>, OrgSource<'s>) -> Res, OrgSource<'s>> +{ + move |context, input: OrgSource<'_>| _line_indented_lte(context, input, indent_level) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -301,11 +318,11 @@ fn item_tag<'r, 's>( context: RefContext<'_, 'r, 's>, input: OrgSource<'s>, ) -> Res, Vec>> { - let parser_context = - context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { - class: ExitClass::Gamma, - exit_matcher: &item_tag_end, - })); + let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Gamma, + exit_matcher: &item_tag_end, + }); + let parser_context = context.with_additional_node(&parser_context); let (remaining, (children, _exit_contents)) = verify( many_till( // TODO: Should this be using a different set like the minimal set? @@ -353,14 +370,16 @@ fn item_tag_post_gap<'r, 's>( #[cfg(test)] mod tests { use super::*; - use crate::parser::parser_context::ContextTree; - use crate::parser::parser_with_context::parser_with_context; - use crate::parser::Source; + use crate::context::Context; + use crate::context::GlobalSettings; + use crate::context::List; #[test] fn plain_list_item_empty() { let input = OrgSource::new("1."); - let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context); let (remaining, result) = plain_list_item_matcher(input).unwrap(); assert_eq!(Into::<&str>::into(remaining), ""); @@ -370,7 +389,9 @@ mod tests { #[test] fn plain_list_item_simple() { let input = OrgSource::new("1. foo"); - let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context); let (remaining, result) = plain_list_item_matcher(input).unwrap(); assert_eq!(Into::<&str>::into(remaining), ""); @@ -380,7 +401,9 @@ mod tests { #[test] fn plain_list_empty() { let input = OrgSource::new("1."); - let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let plain_list_matcher = parser_with_context!(plain_list)(&initial_context); let (remaining, result) = plain_list_matcher(input).unwrap(); assert_eq!(Into::<&str>::into(remaining), ""); @@ -390,7 +413,9 @@ mod tests { #[test] fn plain_list_simple() { let input = OrgSource::new("1. foo"); - let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let plain_list_matcher = parser_with_context!(plain_list)(&initial_context); let (remaining, result) = plain_list_matcher(input).unwrap(); assert_eq!(Into::<&str>::into(remaining), ""); @@ -401,7 +426,9 @@ mod tests { fn plain_list_cant_start_line_with_asterisk() { // Plain lists with an asterisk bullet must be indented or else they would be a headline let input = OrgSource::new("* foo"); - let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let plain_list_matcher = parser_with_context!(plain_list)(&initial_context); let result = plain_list_matcher(input); assert!(result.is_err()); @@ -411,7 +438,9 @@ mod tests { fn indented_can_start_line_with_asterisk() { // Plain lists with an asterisk bullet must be indented or else they would be a headline let input = OrgSource::new(" * foo"); - let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let plain_list_matcher = parser_with_context!(plain_list)(&initial_context); let result = plain_list_matcher(input); assert!(result.is_ok()); @@ -429,7 +458,9 @@ mod tests { ipsum "#, ); - let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let plain_list_matcher = parser_with_context!(element(true))(&initial_context); let (remaining, result) = plain_list_matcher(input).expect("Should parse the plain list successfully."); @@ -455,7 +486,9 @@ mod tests { baz"#, ); - let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let plain_list_matcher = parser_with_context!(element(true))(&initial_context); let (remaining, result) = plain_list_matcher(input).expect("Should parse the plain list successfully."); @@ -486,7 +519,9 @@ baz"#, dolar"#, ); - let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let global_settings = GlobalSettings::default(); + let initial_context = ContextElement::document_context(); + let initial_context = Context::new(&global_settings, List::new(&initial_context)); let plain_list_matcher = parser_with_context!(element(true))(&initial_context); let (remaining, result) = plain_list_matcher(input).expect("Should parse the plain list successfully."); diff --git a/src/parser/target.rs b/src/parser/target.rs index ebb53b7..8f2627b 100644 --- a/src/parser/target.rs +++ b/src/parser/target.rs @@ -7,13 +7,18 @@ use nom::combinator::verify; use nom::multi::many_till; use super::org_source::OrgSource; +use super::util::exit_matcher_parser; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; -use super::Context; +use crate::context::parser_with_context; +use crate::context::ContextElement; +use crate::context::ExitClass; +use crate::context::ExitMatcherNode; +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::Target; +use crate::types::Target; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub fn target<'r, 's>( @@ -25,11 +30,11 @@ pub fn target<'r, 's>( !c.is_whitespace() && !"<>\n".contains(*c) }))(remaining)?; - let parser_context = - context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { - class: ExitClass::Beta, - exit_matcher: &target_end, - })); + let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Beta, + exit_matcher: &target_end, + }); + let parser_context = context.with_additional_node(&parser_context); let (remaining, _body) = recognize(many_till( anychar, parser_with_context!(exit_matcher_parser)(&parser_context), diff --git a/src/parser/text_markup.rs b/src/parser/text_markup.rs index 8b67b15..3b976ef 100644 --- a/src/parser/text_markup.rs +++ b/src/parser/text_markup.rs @@ -20,7 +20,6 @@ use super::org_source::OrgSource; use super::radio_link::RematchObject; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use crate::context::parser_with_context; -use crate::context::Context; use crate::context::ContextElement; use crate::context::ExitClass; use crate::context::ExitMatcherNode;