use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::line_ending; use nom::character::complete::space1; use nom::combinator::eof; use nom::combinator::map; use nom::combinator::not; use nom::combinator::opt; use nom::combinator::recognize; use nom::combinator::verify; use nom::multi::many0; use nom::multi::many1; use nom::multi::many1_count; use nom::multi::many_till; use nom::sequence::tuple; use crate::parser::element::element; use crate::parser::object::standard_set_object; use crate::parser::parser_context::ChainBehavior; use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextTree; use crate::parser::parser_context::ExitMatcherNode; use crate::parser::util::element_trailing_whitespace; use super::element::Element; use super::error::Res; use super::object::Object; use super::parser_with_context::parser_with_context; use super::source::Source; use super::util::exit_matcher_parser; use super::util::get_consumed; use super::util::start_of_line; use super::util::trailing_whitespace; use super::Context; #[derive(Debug)] pub struct Document<'s> { pub source: &'s str, pub zeroth_section: Option>, pub children: Vec>, } #[derive(Debug)] pub struct Heading<'s> { pub source: &'s str, pub stars: usize, pub title: Vec>, pub children: Vec>, } #[derive(Debug)] pub struct Section<'s> { pub source: &'s str, pub children: Vec>, } #[derive(Debug)] pub enum DocumentElement<'s> { Heading(Heading<'s>), Section(Section<'s>), } impl<'s> Source<'s> for Document<'s> { fn get_source(&'s self) -> &'s str { self.source } } impl<'s> Source<'s> for DocumentElement<'s> { fn get_source(&'s self) -> &'s str { match self { DocumentElement::Heading(obj) => obj.source, DocumentElement::Section(obj) => obj.source, } } } #[tracing::instrument(ret, level = "debug")] #[allow(dead_code)] pub fn document(input: &str) -> Res<&str, Document> { let initial_context: ContextTree<'_, '_> = ContextTree::new(); let document_context = initial_context.with_additional_node(ContextElement::DocumentRoot(input)); let section_matcher = parser_with_context!(section)(&document_context); let heading_matcher = parser_with_context!(heading)(&document_context); let (remaining, zeroth_section) = opt(section_matcher)(input)?; let (remaining, children) = many0(heading_matcher)(remaining)?; let source = get_consumed(input, remaining); Ok(( remaining, Document { source, zeroth_section, children, }, )) } #[tracing::instrument(ret, level = "debug")] fn section<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, 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 { exit_matcher: ChainBehavior::AndParent(Some(§ion_end)), })); let element_matcher = parser_with_context!(element)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let trailing_matcher = parser_with_context!(element_trailing_whitespace)(&parser_context); let (remaining, (children, _exit_contents)) = verify( many_till( tuple(( element_matcher, opt(map(trailing_matcher, Element::TrailingWhitespace)), )), exit_matcher, ), |(children, _exit_contents)| !children.is_empty(), )(input)?; let flattened_children: Vec = children .into_iter() .flat_map(|tpl| { let mut flattened_children = Vec::with_capacity(2); flattened_children.push(tpl.0); if let Some(bar) = tpl.1 { flattened_children.push(bar); } flattened_children.into_iter() }) .collect(); let source = get_consumed(input, remaining); Ok(( remaining, Section { source, children: flattened_children, }, )) } #[tracing::instrument(ret, level = "debug")] fn section_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { let headline_matcher = parser_with_context!(headline)(context); alt((recognize(headline_matcher), eof))(input) } #[tracing::instrument(ret, level = "debug")] fn heading<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Heading<'s>> { not(|i| context.check_exit_matcher(i))(input)?; let (remaining, (star_count, _ws, title, _ws2)) = headline(context, input)?; let section_matcher = parser_with_context!(section)(context); let heading_matcher = parser_with_context!(heading)(context); let (remaining, children) = many0(alt(( map( verify(heading_matcher, |h| h.stars > star_count), DocumentElement::Heading, ), map(section_matcher, DocumentElement::Section), )))(remaining)?; let source = get_consumed(input, remaining); Ok(( remaining, Heading { source, stars: star_count, title, children, }, )) } #[tracing::instrument(ret, level = "debug")] fn headline<'r, 's>( context: Context<'r, 's>, input: &'s str, ) -> Res<&'s str, (usize, &'s str, Vec>, &'s str)> { let parser_context = context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { exit_matcher: ChainBehavior::AndParent(Some(&headline_end)), })); let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context); let start_of_line_matcher = parser_with_context!(start_of_line)(&parser_context); let (remaining, (_sol, star_count, ws, title, ws2)) = tuple(( start_of_line_matcher, many1_count(tag("*")), space1, many1(standard_set_object_matcher), trailing_whitespace, ))(input)?; Ok((remaining, (star_count, ws, title, ws2))) } #[tracing::instrument(ret, level = "debug")] fn headline_end<'r, 's>(_context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { alt((line_ending, eof))(input) }