use super::sexp::Token; use super::util::assert_bounds; use super::util::assert_name; use crate::parser::Comment; use crate::parser::CommentBlock; use crate::parser::Document; use crate::parser::DocumentElement; use crate::parser::Drawer; use crate::parser::Element; use crate::parser::ExampleBlock; use crate::parser::ExportBlock; use crate::parser::FootnoteDefinition; use crate::parser::GreaterBlock; use crate::parser::Heading; use crate::parser::Paragraph; use crate::parser::PlainList; use crate::parser::PlainListItem; use crate::parser::PropertyDrawer; use crate::parser::Section; use crate::parser::SrcBlock; use crate::parser::Table; use crate::parser::TableCell; use crate::parser::TableRow; use crate::parser::VerseBlock; use crate::DynamicBlock; #[derive(Debug)] pub struct DiffResult { status: DiffStatus, name: String, #[allow(dead_code)] message: Option, children: Vec, } #[derive(Debug, PartialEq)] pub enum DiffStatus { Good, Bad, } impl DiffResult { pub fn print(&self) -> Result<(), Box> { self.print_indented(0) } fn print_indented(&self, indentation: usize) -> Result<(), Box> { let status_text = { match self.status { DiffStatus::Good => { if self.has_bad_children() { "BADCHILD" } else { "GOOD" } } DiffStatus::Bad => "BAD", } }; println!("{}{} {}", " ".repeat(indentation), status_text, self.name); for child in self.children.iter() { child.print_indented(indentation + 1)?; } Ok(()) } pub fn has_bad_children(&self) -> bool { self.children .iter() .any(|child| child.status == DiffStatus::Bad || child.has_bad_children()) } pub fn is_bad(&self) -> bool { match self.status { DiffStatus::Good => self.has_bad_children(), DiffStatus::Bad => true, } } } pub fn compare_document<'s>( emacs: &'s Token<'s>, rust: &'s Document<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "org-data"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } // Skipping "org-data" and the first parameter which is often nil for (i, token) in children.iter().skip(2).enumerate() { let section_or_headline = token.as_list()?; let first_cell = section_or_headline .first() .ok_or("Should have at least one child.")? .as_atom()?; if first_cell == "section" { if i != 0 { return Err("Section cannot be after the first child of document.".into()); } child_status.push(compare_section( rust.source, token, rust.zeroth_section .as_ref() .ok_or("No corresponding zeroth-section")?, )?); } else if first_cell == "headline" { let corresponding_heading = rust .children .iter() .nth(i - rust.zeroth_section.as_ref().map(|_| 1).unwrap_or(0)) .ok_or("Should have a corresponding heading.")?; child_status.push(compare_heading(rust.source, token, corresponding_heading)?); } else { return Err("Document should only contain sections and headlines.".into()); } } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_section<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s Section<'s>, ) -> Result> { let children = emacs.as_list()?; let mut this_status = DiffStatus::Good; let mut child_status = Vec::new(); let emacs_name = "section"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { 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: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_heading<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s Heading<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "headline"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { match rust_child { DocumentElement::Heading(rust_heading) => { child_status.push(compare_heading(source, emacs_child, rust_heading)?); } DocumentElement::Section(rust_section) => { child_status.push(compare_section(source, emacs_child, rust_section)?); } }; } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_element<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s Element<'s>, ) -> Result> { match rust { 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), Element::PropertyDrawer(obj) => compare_property_drawer(source, emacs, obj), Element::Table(obj) => compare_table(source, emacs, obj), Element::VerseBlock(obj) => compare_verse_block(source, emacs, obj), Element::CommentBlock(obj) => compare_comment_block(source, emacs, obj), Element::ExampleBlock(obj) => compare_example_block(source, emacs, obj), Element::ExportBlock(obj) => compare_export_block(source, emacs, obj), Element::SrcBlock(obj) => compare_src_block(source, emacs, obj), } } fn compare_paragraph<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s Paragraph<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "paragraph"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {} Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_plain_list<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s PlainList<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "plain-list"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_plain_list_item(source, emacs_child, rust_child)?); } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_plain_list_item<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s PlainListItem<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "item"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { 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: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_greater_block<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s GreaterBlock<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = match rust.name.to_lowercase().as_str() { "center" => "center-block", "quote" => "quote-block", _ => todo!(), }; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { 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: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_dynamic_block<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s DynamicBlock<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; if assert_name(emacs, "dynamic-block").is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { 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(), message: None, children: child_status, }) } fn compare_footnote_definition<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s FootnoteDefinition<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "footnote-definition"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { 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: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_comment<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s Comment<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "comment"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_drawer<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s Drawer<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "drawer"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { 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: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_property_drawer<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s PropertyDrawer<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "property-drawer"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { // TODO: What are node properties and are they the only legal child of property drawers? // child_status.push(compare_element(source, emacs_child, rust_child)?); } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_table<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s Table<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "table"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_table_row(source, emacs_child, rust_child)?); } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_table_row<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s TableRow<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "table-row"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { child_status.push(compare_table_cell(source, emacs_child, rust_child)?); } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_table_cell<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s TableCell<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "table-cell"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {} Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_verse_block<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s VerseBlock<'s>, ) -> Result> { let children = emacs.as_list()?; let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; let emacs_name = "verse-block"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {} Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: child_status, }) } fn compare_comment_block<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s CommentBlock<'s>, ) -> Result> { let mut this_status = DiffStatus::Good; let emacs_name = "comment-block"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: Vec::new(), }) } fn compare_example_block<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s ExampleBlock<'s>, ) -> Result> { let mut this_status = DiffStatus::Good; let emacs_name = "example-block"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: Vec::new(), }) } fn compare_export_block<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s ExportBlock<'s>, ) -> Result> { let mut this_status = DiffStatus::Good; let emacs_name = "export-block"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: Vec::new(), }) } fn compare_src_block<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s SrcBlock<'s>, ) -> Result> { let mut this_status = DiffStatus::Good; let emacs_name = "src-block"; if assert_name(emacs, emacs_name).is_err() { this_status = DiffStatus::Bad; } if assert_bounds(source, emacs, rust).is_err() { this_status = DiffStatus::Bad; } Ok(DiffResult { status: this_status, name: emacs_name.to_owned(), message: None, children: Vec::new(), }) }