use super::sexp::Token; use crate::compare::util::get_offsets; use crate::parser::Document; use crate::parser::DocumentElement; use crate::parser::Heading; use crate::parser::Section; #[derive(Debug)] pub struct DiffResult { status: DiffStatus, name: String, 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 compare_document<'s>( emacs: &'s Token<'s>, rust: &'s Document<'s>, ) -> Result> { let children = emacs.as_list()?; let first_child = children.first().ok_or("Should have at least one child.")?; let first_child_text = first_child.as_atom()?; if first_child_text != "org-data" { return Err("Document should correspond to an org-data cell.".into()); } let mut child_status = Vec::new(); let mut this_status = DiffStatus::Good; // 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: "document".to_owned(), children: child_status, }) } pub fn compare_section<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s Section<'s>, ) -> Result> { let children = emacs.as_list()?; let first_child = children.first().ok_or("Should have at least one child.")?; let first_child_text = first_child.as_atom()?; if first_child_text != "section" { return Err("Section should correspond to a section 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; } Ok(DiffResult { status: this_status, name: "section".to_owned(), children: child_status, }) } pub fn compare_heading<'s>( source: &'s str, emacs: &'s Token<'s>, rust: &'s Heading<'s>, ) -> Result> { let children = emacs.as_list()?; let first_child = children.first().ok_or("Should have at least one child.")?; let first_child_text = first_child.as_atom()?; if first_child_text != "headline" { return Err("Heading should correspond to a headline 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()) { 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: "heading".to_owned(), children: child_status, }) }