diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 0a0dd08..66dfa06 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -3,6 +3,7 @@ use crate::compare::util::get_offsets; use crate::parser::Comment; use crate::parser::Document; use crate::parser::DocumentElement; +use crate::parser::Drawer; use crate::parser::Element; use crate::parser::FootnoteDefinition; use crate::parser::GreaterBlock; @@ -11,7 +12,7 @@ use crate::parser::Paragraph; use crate::parser::PlainList; use crate::parser::PlainListItem; use crate::parser::Section; -use crate::parser::Drawer; +use crate::DynamicBlock; #[derive(Debug)] pub struct DiffResult { @@ -217,6 +218,7 @@ fn compare_element<'s>( Element::Paragraph(obj) => compare_paragraph(source, emacs, obj), Element::PlainList(obj) => compare_plain_list(source, emacs, obj), Element::GreaterBlock(obj) => compare_greater_block(source, emacs, obj), + Element::DynamicBlock(obj) => compare_dynamic_block(source, emacs, obj), Element::FootnoteDefinition(obj) => compare_footnote_definition(source, emacs, obj), Element::Comment(obj) => compare_comment(source, emacs, obj), Element::Drawer(obj) => compare_drawer(source, emacs, obj), @@ -413,6 +415,51 @@ fn compare_greater_block<'s>( }) } +fn compare_dynamic_block<'s>( + source: &'s str, + emacs: &'s Token<'s>, + rust: &'s DynamicBlock<'s>, +) -> Result> { + let children = emacs.as_list()?; + let first_child = children + .first() + .ok_or("Should have at least one child.")? + .as_atom()?; + if first_child != "dynamic-block" { + return Err("Dynamic block should correspond to a dynamic-block cell.".into()); + } + let mut child_status = Vec::new(); + let mut this_status = DiffStatus::Good; + + let attributes_child = children + .iter() + .nth(1) + .ok_or("Should have an attributes child.")?; + let attributes_map = attributes_child.as_map()?; + let begin = attributes_map + .get(":begin") + .ok_or("Missing :begin attribute.")? + .as_atom()?; + let end = attributes_map + .get(":end") + .ok_or("Missing :end attribute.")? + .as_atom()?; + let (rust_begin, rust_end) = get_offsets(source, rust); + if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end { + this_status = DiffStatus::Bad; + } + + for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { + child_status.push(compare_element(source, emacs_child, rust_child)?); + } + + Ok(DiffResult { + status: this_status, + name: "dynamic-block".to_owned(), + children: child_status, + }) +} + fn compare_footnote_definition<'s>( source: &'s str, emacs: &'s Token<'s>, diff --git a/src/parser/dynamic_block.rs b/src/parser/dynamic_block.rs new file mode 100644 index 0000000..2e3412a --- /dev/null +++ b/src/parser/dynamic_block.rs @@ -0,0 +1,111 @@ +use super::error::Res; +use super::Context; +use crate::parser::element::element; +use crate::parser::error::CustomError; +use crate::parser::error::MyError; +use crate::parser::exiting::ExitClass; +use crate::parser::greater_element::DynamicBlock; +use crate::parser::lesser_element::Paragraph; +use crate::parser::parser_context::ContextElement; +use crate::parser::parser_context::ExitMatcherNode; +use crate::parser::parser_with_context::parser_with_context; +use crate::parser::util::blank_line; +use crate::parser::util::exit_matcher_parser; +use crate::parser::util::get_consumed; +use crate::parser::util::immediate_in_section; +use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; +use crate::parser::util::start_of_line; +use crate::parser::Element; +use nom::branch::alt; +use nom::bytes::complete::is_not; +use nom::bytes::complete::tag_no_case; +use nom::character::complete::line_ending; +use nom::character::complete::space0; +use nom::character::complete::space1; +use nom::combinator::consumed; +use nom::combinator::eof; +use nom::combinator::opt; +use nom::combinator::recognize; +use nom::multi::many_till; +use nom::sequence::tuple; + +#[tracing::instrument(ret, level = "debug")] +pub fn dynamic_block<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, DynamicBlock<'s>> { + // TODO: Do I need to differentiate between different dynamic block types. + if immediate_in_section(context, "dynamic block") { + return Err(nom::Err::Error(CustomError::MyError(MyError( + "Cannot nest objects of the same element", + )))); + } + start_of_line(context, input)?; + let (remaining, _leading_whitespace) = space0(input)?; + let (remaining, (_begin, name, parameters, _ws)) = tuple(( + recognize(tuple((tag_no_case("#+begin:"), space1))), + name, + opt(tuple((space1, parameters))), + line_ending, + ))(remaining)?; + let parser_context = context + .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) + .with_additional_node(ContextElement::Context("dynamic block")) + .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Alpha, + exit_matcher: &dynamic_block_end, + })); + let parameters = match parameters { + Some((_ws, parameters)) => Some(parameters), + None => None, + }; + let element_matcher = parser_with_context!(element)(&parser_context); + let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); + let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) { + Ok((remaining, (whitespace, (_children, _exit_contents)))) => ( + remaining, + vec![Element::Paragraph(Paragraph::of_text(whitespace))], + ), + Err(_) => { + let (remaining, (children, _exit_contents)) = + many_till(element_matcher, exit_matcher)(remaining)?; + (remaining, children) + } + }; + let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?; + + let (remaining, _trailing_ws) = + maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; + + let source = get_consumed(input, remaining); + Ok(( + remaining, + DynamicBlock { + source, + name, + parameters, + children, + }, + )) +} + +#[tracing::instrument(ret, level = "debug")] +fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> { + is_not(" \t\r\n")(input) +} + +#[tracing::instrument(ret, level = "debug")] +fn parameters<'s>(input: &'s str) -> Res<&'s str, &'s str> { + is_not("\r\n")(input) +} + +#[tracing::instrument(ret, level = "debug")] +fn dynamic_block_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { + start_of_line(context, input)?; + let (remaining, source) = recognize(tuple(( + space0, + tag_no_case("#+end:"), + alt((eof, line_ending)), + )))(input)?; + Ok((remaining, source)) +} diff --git a/src/parser/element.rs b/src/parser/element.rs index 189d62d..ecf9a80 100644 --- a/src/parser/element.rs +++ b/src/parser/element.rs @@ -1,8 +1,10 @@ use super::comment::comment; use super::drawer::drawer; +use super::dynamic_block::dynamic_block; use super::error::Res; use super::footnote_definition::footnote_definition; use super::greater_block::greater_block; +use super::greater_element::DynamicBlock; use super::greater_element::FootnoteDefinition; use super::greater_element::GreaterBlock; use super::greater_element::PlainList; @@ -23,6 +25,7 @@ pub enum Element<'s> { Paragraph(Paragraph<'s>), PlainList(PlainList<'s>), GreaterBlock(GreaterBlock<'s>), + DynamicBlock(DynamicBlock<'s>), FootnoteDefinition(FootnoteDefinition<'s>), Comment(Comment<'s>), Drawer(Drawer<'s>), @@ -34,6 +37,7 @@ impl<'s> Source<'s> for Element<'s> { Element::Paragraph(obj) => obj.source, Element::PlainList(obj) => obj.source, Element::GreaterBlock(obj) => obj.source, + Element::DynamicBlock(obj) => obj.source, Element::FootnoteDefinition(obj) => obj.source, Element::Comment(obj) => obj.source, Element::Drawer(obj) => obj.source, @@ -65,6 +69,12 @@ impl<'s> Source<'s> for GreaterBlock<'s> { } } +impl<'s> Source<'s> for DynamicBlock<'s> { + fn get_source(&'s self) -> &'s str { + self.source + } +} + impl<'s> Source<'s> for FootnoteDefinition<'s> { fn get_source(&'s self) -> &'s str { self.source @@ -100,12 +110,14 @@ pub fn non_paragraph_element<'r, 's>( ) -> Res<&'s str, Element<'s>> { let plain_list_matcher = parser_with_context!(plain_list)(context); let greater_block_matcher = parser_with_context!(greater_block)(context); + let dynamic_block_matcher = parser_with_context!(dynamic_block)(context); let footnote_definition_matcher = parser_with_context!(footnote_definition)(context); let comment_matcher = parser_with_context!(comment)(context); let drawer_matcher = parser_with_context!(drawer)(context); alt(( map(plain_list_matcher, Element::PlainList), map(greater_block_matcher, Element::GreaterBlock), + map(dynamic_block_matcher, Element::DynamicBlock), map(footnote_definition_matcher, Element::FootnoteDefinition), map(comment_matcher, Element::Comment), map(drawer_matcher, Element::Drawer), diff --git a/src/parser/greater_block.rs b/src/parser/greater_block.rs index 2061e48..73a7deb 100644 --- a/src/parser/greater_block.rs +++ b/src/parser/greater_block.rs @@ -1,12 +1,10 @@ use super::error::Res; -use super::object::TextMarkup; use super::Context; use crate::parser::element::element; use crate::parser::error::CustomError; use crate::parser::error::MyError; use crate::parser::exiting::ExitClass; use crate::parser::greater_element::GreaterBlock; -use crate::parser::object::Object; use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_with_context::parser_with_context; @@ -69,10 +67,9 @@ pub fn greater_block<'r, 's>( let element_matcher = parser_with_context!(element)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); - // TODO: Not handling nested greater blocks // Check for a completely empty block let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) { - Ok((remaining, (whitespace, (children, _exit_contents)))) => ( + Ok((remaining, (whitespace, (_children, _exit_contents)))) => ( remaining, vec![Element::Paragraph(Paragraph::of_text(whitespace))], ), diff --git a/src/parser/greater_element.rs b/src/parser/greater_element.rs index c9e5975..9018726 100644 --- a/src/parser/greater_element.rs +++ b/src/parser/greater_element.rs @@ -22,6 +22,14 @@ pub struct GreaterBlock<'s> { pub children: Vec>, } +#[derive(Debug)] +pub struct DynamicBlock<'s> { + pub source: &'s str, + pub name: &'s str, + pub parameters: Option<&'s str>, + pub children: Vec>, +} + #[derive(Debug)] pub struct FootnoteDefinition<'s> { pub source: &'s str, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 8f63930..3a2eb61 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,6 +1,7 @@ mod comment; mod document; mod drawer; +mod dynamic_block; mod element; mod error; mod exiting; @@ -24,6 +25,7 @@ pub use document::Heading; pub use document::Section; pub use element::Element; pub use greater_element::Drawer; +pub use greater_element::DynamicBlock; pub use greater_element::FootnoteDefinition; pub use greater_element::GreaterBlock; pub use greater_element::PlainList;