use std::borrow::Borrow; use std::borrow::Cow; use std::collections::BTreeSet; use std::fmt::Debug; use std::str::FromStr; use super::diff::artificial_diff_scope; use super::diff::artificial_owned_diff_scope; use super::diff::compare_ast_node; use super::diff::DiffEntry; use super::diff::DiffStatus; use super::sexp::unquote; use super::sexp::Token; use super::util::get_property; use super::util::get_property_numeric; use super::util::get_property_quoted_string; use super::util::get_property_unquoted_atom; use crate::types::AstNode; use crate::types::CharOffsetInLine; use crate::types::LineNumber; use crate::types::RetainLabels; use crate::types::SwitchNumberLines; #[derive(Debug)] pub(crate) enum EmacsField<'s> { Required(&'s str), Optional(&'s str), } #[derive(Debug)] pub(crate) enum ComparePropertiesResult<'b, 's> { NoChange, /// Return when you want the status for "this" node to change (as opposed to collecting child status). SelfChange(DiffStatus, Option), DiffEntry(DiffEntry<'b, 's>), } impl<'b, 's> ComparePropertiesResult<'b, 's> { pub(crate) fn apply( self, child_status: &mut Vec>, this_status: &mut DiffStatus, message: &mut Option, ) { match self { ComparePropertiesResult::NoChange => {} ComparePropertiesResult::SelfChange(new_status, new_message) => { *this_status = new_status; *message = new_message } ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry), } // foo } } /// Do no comparison. /// /// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values. pub(crate) fn compare_noop<'b, 's, R, RG>( _source: &'s str, _emacs: &'b Token<'s>, _rust_node: R, _emacs_field: &str, _rust_value_getter: RG, ) -> Result, Box> { Ok(ComparePropertiesResult::NoChange) } /// Do no comparison. /// /// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values. pub(crate) fn compare_identity() {} /// Assert that the emacs value is always nil or absent. /// /// This is usually used for fields which, in my testing, are always nil. Using this compare function instead of simply doing a compare_noop will enable us to be alerted when we finally come across an org-mode document that has a value other than nil for the property. pub(crate) fn compare_property_always_nil<'b, 's, R, RG>( _source: &'s str, emacs: &'b Token<'s>, _rust_node: R, emacs_field: &str, _rust_value_getter: RG, ) -> Result, Box> { let value = get_property(emacs, emacs_field)?; if value.is_some() { let this_status = DiffStatus::Bad; let message = Some(format!( "{} was expected to always be nil: {:?}", emacs_field, value )); Ok(ComparePropertiesResult::SelfChange(this_status, message)) } else { Ok(ComparePropertiesResult::NoChange) } } pub(crate) fn compare_property_quoted_string< 'b, 's, R, RV: AsRef + std::fmt::Debug, RG: Fn(R) -> Option, >( _source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &str, rust_value_getter: RG, ) -> Result, Box> { let value = get_property_quoted_string(emacs, emacs_field)?; let rust_value = rust_value_getter(rust_node); if rust_value.as_ref().map(|s| s.as_ref()) != value.as_deref() { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rust_value )); Ok(ComparePropertiesResult::SelfChange(this_status, message)) } else { Ok(ComparePropertiesResult::NoChange) } } pub(crate) fn compare_property_unquoted_atom<'b, 's, 'x, R, RG: Fn(R) -> Option<&'s str>>( _source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &'x str, rust_value_getter: RG, ) -> Result, Box> { let value = get_property_unquoted_atom(emacs, emacs_field)?; let rust_value = rust_value_getter(rust_node); if rust_value != value { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rust_value )); Ok(ComparePropertiesResult::SelfChange(this_status, message)) } else { Ok(ComparePropertiesResult::NoChange) } } pub(crate) fn compare_property_numeric< 'b, 's, 'x, R, RV: FromStr + PartialEq + Debug, RG: Fn(R) -> Option, >( _source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &'x str, rust_value_getter: RG, ) -> Result, Box> where ::Err: std::error::Error, ::Err: 's, { let value = get_property_numeric::(emacs, emacs_field)?; let rust_value = rust_value_getter(rust_node); if rust_value != value { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rust_value )); Ok(ComparePropertiesResult::SelfChange(this_status, message)) } else { Ok(ComparePropertiesResult::NoChange) } } pub(crate) fn compare_property_list_of_quoted_string< 'b, 's, R, RV: AsRef + std::fmt::Debug, RI: Iterator, RG: Fn(R) -> Option, >( _source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &str, rust_value_getter: RG, ) -> Result, Box> { let value = get_property(emacs, emacs_field)? .map(Token::as_list) .map_or(Ok(None), |r| r.map(Some))?; let rust_value = rust_value_getter(rust_node); // TODO: Seems we are needlessly coverting to a vec here. let rust_value: Option> = rust_value.map(|it| it.collect()); match (value, &rust_value) { (None, None) => {} (None, Some(_)) | (Some(_), None) => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(el), Some(rl)) if el.len() != rl.len() => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(el), Some(rl)) => { for (e, r) in el.iter().zip(rl) { let e = unquote(e.as_atom()?)?; let r = r.as_ref(); if e != r { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}", emacs_field, e, r, value, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } } } } Ok(ComparePropertiesResult::NoChange) } pub(crate) fn compare_property_set_of_quoted_string< 'a, 'b, 's, 'x, R, RV: AsRef + std::fmt::Debug + Ord + 'a + ?Sized, RI: Iterator, RG: Fn(R) -> Option, >( _source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &'x str, rust_value_getter: RG, ) -> Result, Box> { let value = get_property(emacs, emacs_field)? .map(Token::as_list) .map_or(Ok(None), |r| r.map(Some))?; let empty = Vec::new(); let value = value.unwrap_or(&empty); let rust_value = rust_value_getter(rust_node); let rust_value = if let Some(rust_value) = rust_value { let slices: BTreeSet<&str> = rust_value.map(|rv| rv.as_ref()).collect(); slices } else { BTreeSet::new() }; let value: Vec<&str> = value .iter() .map(|e| e.as_atom()) .collect::, _>>()?; let value: Vec> = value .into_iter() .map(unquote) .collect::, _>>()?; let value: BTreeSet<&str> = value.iter().map(|e| e.borrow()).collect(); let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect(); if !mismatched.is_empty() { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch. Mismatched values: {}", emacs_field, mismatched.join(", ") )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } Ok(ComparePropertiesResult::NoChange) } pub(crate) fn compare_property_optional_pair< 'b, 's, R, RV: AsRef + std::fmt::Debug, ROV: AsRef + std::fmt::Debug, RG: Fn(R) -> Option<(Option, RV)>, >( _source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &str, rust_value_getter: RG, ) -> Result, Box> { let value = get_property(emacs, emacs_field)? .map(Token::as_list) .map_or(Ok(None), |r| r.map(Some))?; let rust_value = rust_value_getter(rust_node); match (value, &rust_value) { (None, None) => {} (None, Some(_)) | (Some(_), None) => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(el), Some((Some(_), _))) if el.len() != 3 => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(el), Some((None, _))) if el.len() != 1 => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(el), Some((Some(orl), rl))) => { let e = el .first() .map(Token::as_atom) .map_or(Ok(None), |r| r.map(Some))? .map(unquote) .map_or(Ok(None), |r| r.map(Some))? .expect("Above match proved length to be 3."); let oe = el .get(2) .map(Token::as_atom) .map_or(Ok(None), |r| r.map(Some))? .map(unquote) .map_or(Ok(None), |r| r.map(Some))? .expect("Above match proved length to be 3."); let r = rl.as_ref(); let or = orl.as_ref(); if e != r { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}", emacs_field, e, r, value, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } if oe != or { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}", emacs_field, e, r, value, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } } (Some(el), Some((None, rl))) => { let e = el .first() .map(Token::as_atom) .map_or(Ok(None), |r| r.map(Some))? .map(unquote) .map_or(Ok(None), |r| r.map(Some))? .expect("Above match proved length to be 1."); let r = rl.as_ref(); if e != r { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}", emacs_field, e, r, value, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } } } Ok(ComparePropertiesResult::NoChange) } pub(crate) fn compare_property_boolean<'b, 's, R, RG: Fn(R) -> bool>( _source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &str, rust_value_getter: RG, ) -> Result, Box> { // get_property already converts nil to None. let value = get_property(emacs, emacs_field)?.is_some(); let rust_value = rust_value_getter(rust_node); if rust_value != value { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rust_value )); Ok(ComparePropertiesResult::SelfChange(this_status, message)) } else { Ok(ComparePropertiesResult::NoChange) } } pub(crate) fn compare_property_single_ast_node< 'b, 's, 'x: 'b + 's, R, RV: std::fmt::Debug, RG: Fn(R) -> Option, >( source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &'x str, rust_value_getter: RG, ) -> Result, Box> where AstNode<'b, 's>: From, { let value = get_property(emacs, emacs_field)?; let rust_value = rust_value_getter(rust_node); match (value, rust_value) { (None, None) => {} (None, rv @ Some(_)) | (Some(_), rv @ None) => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rv )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(ev), Some(rv)) => { let child_status: Vec> = vec![compare_ast_node(source, ev, rv.into())?]; let diff_scope = artificial_diff_scope(emacs_field, child_status)?; return Ok(ComparePropertiesResult::DiffEntry(diff_scope)); } } Ok(ComparePropertiesResult::NoChange) } pub(crate) fn compare_property_list_of_ast_nodes< 'b, 's, 'x: 'b + 's, R, RV: std::fmt::Debug, RI: Iterator, RG: Fn(R) -> Option, >( source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &'x str, rust_value_getter: RG, ) -> Result, Box> where AstNode<'b, 's>: From, { let value = get_property(emacs, emacs_field)? .map(Token::as_list) .map_or(Ok(None), |r| r.map(Some))?; let rust_value = rust_value_getter(rust_node); // TODO: Seems we are needlessly coverting to a vec here. let rust_value: Option> = rust_value.map(|it| it.collect()); match (value, rust_value) { (None, None) => {} (Some(el), None) if el.len() == 1 && el.iter().all(|t| matches!(t.as_atom(), Ok(r#""""#))) => {} (None, rv @ Some(_)) | (Some(_), rv @ None) => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rv )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(el), Some(rl)) if el.len() != rl.len() => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, el, rl )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(el), Some(rl)) => { let mut child_status: Vec> = Vec::with_capacity(rl.len()); for (e, r) in el.iter().zip(rl) { child_status.push(compare_ast_node(source, e, r.into())?); } let diff_scope = artificial_diff_scope(emacs_field, child_status)?; return Ok(ComparePropertiesResult::DiffEntry(diff_scope)); } } Ok(ComparePropertiesResult::NoChange) } pub(crate) fn compare_property_object_tree< 'b, 's, 'x, R, RV: std::fmt::Debug + 'b, ROV: std::fmt::Debug + 'b, RI: Iterator>, Vec)> + ExactSizeIterator + std::fmt::Debug, RG: Fn(R) -> Option, >( source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &'x str, rust_value_getter: RG, ) -> Result, Box> where AstNode<'b, 's>: From<&'b RV>, AstNode<'b, 's>: From<&'b ROV>, { let value = get_property(emacs, emacs_field)? .map(Token::as_list) .map_or(Ok(None), |r| r.map(Some))?; let rust_value = rust_value_getter(rust_node); let (outer_emacs_list, outer_rust_list) = match (value, rust_value) { (None, None) => { return Ok(ComparePropertiesResult::NoChange); } (None, rv @ Some(_)) | (Some(_), rv @ None) => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, value, rv )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(el), Some(rl)) if el.len() != rl.len() => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, el, rl )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } (Some(el), Some(rl)) => (el, rl), }; let mut full_status: Vec> = Vec::with_capacity(outer_rust_list.len()); for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) { match (kw_e.as_atom(), kw_r) { (Ok("nil"), (None, mandatory_value)) if mandatory_value.is_empty() => { // If its an empty keyword then it becomes nil in the elisp. continue; } (Ok("nil"), _) => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, kw_e, kw_r )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } _ => {} } let kw_e = kw_e.as_list()?; let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0); let mut child_status: Vec> = Vec::with_capacity(child_status_length); if let Some(or) = &kw_r.0 { // if optional value let mut kw_e = kw_e.iter(); // First element is a list representing the mandatory value. if let Some(val_e) = kw_e.next() { match (val_e.as_atom(), kw_r) { (Ok("nil"), (_, mandatory_value)) if mandatory_value.is_empty() => {} (Ok("nil"), _) => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, kw_e, kw_r )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } _ => { let el = val_e.as_list()?; if el.len() != kw_r.1.len() { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, kw_e, kw_r )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } for (e, r) in el.iter().zip(kw_r.1.iter()) { child_status.push(compare_ast_node(source, e, r.into())?); } } }; } else { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, kw_e, kw_r )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } // Remaining elements are the optional value. if kw_e.len() != or.len() { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, kw_e, kw_r )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } for (e, r) in kw_e.zip(or.iter()) { child_status.push(compare_ast_node(source, e, r.into())?); } } else { // if no optional value if !kw_e.len() == 1 { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, kw_e, kw_r )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } let e = kw_e .first() .map(Token::as_list) .map_or(Ok(None), |r| r.map(Some))? .expect("The above if-statement proves this will be Some.") .iter(); let r = kw_r.1.iter(); if e.len() != r.len() { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, kw_e, kw_r )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } for (e, r) in e.zip(r) { child_status.push(compare_ast_node(source, e, r.into())?); } } if !child_status.is_empty() { let diff_scope = artificial_diff_scope("mandatory value", child_status)?; full_status.push(diff_scope); } } if full_status.is_empty() { Ok(ComparePropertiesResult::NoChange) } else { let diff_scope = artificial_owned_diff_scope(emacs_field, full_status)?; Ok(ComparePropertiesResult::DiffEntry(diff_scope)) } } pub(crate) fn compare_property_number_lines< 'b, 's, 'x, 'y, R, RG: Fn(R) -> Option<&'y SwitchNumberLines>, >( _source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &'x str, rust_value_getter: RG, ) -> Result, Box> { let number_lines = get_property(emacs, emacs_field)?; let rust_value = rust_value_getter(rust_node); match (number_lines, &rust_value) { (None, None) => {} (Some(number_lines), Some(rust_number_lines)) => { let token_list = number_lines.as_list()?; let number_type = token_list .first() .map(Token::as_atom) .map_or(Ok(None), |r| r.map(Some))? .ok_or(":number-lines should have a type.")?; let number_value = token_list .get(2) .map(Token::as_atom) .map_or(Ok(None), |r| r.map(Some))? .map(|val| val.parse::()) .map_or(Ok(None), |r| r.map(Some))? .ok_or(":number-lines should have a value.")?; match (number_type, number_value, rust_number_lines) { ("new", emacs_val, SwitchNumberLines::New(rust_val)) if emacs_val == *rust_val => {} ("continued", emacs_val, SwitchNumberLines::Continued(rust_val)) if emacs_val == *rust_val => {} _ => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, number_lines, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } } } _ => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, number_lines, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } }; Ok(ComparePropertiesResult::NoChange) } pub(crate) fn compare_property_retain_labels<'b, 's, 'x, 'y, R, RG: Fn(R) -> &'y RetainLabels>( _source: &'s str, emacs: &'b Token<'s>, rust_node: R, emacs_field: &'x str, rust_value_getter: RG, ) -> Result, Box> { let rust_value = rust_value_getter(rust_node); let retain_labels = get_property_unquoted_atom(emacs, ":retain-labels")?; if let Some(retain_labels) = retain_labels { if retain_labels == "t" { match rust_value { RetainLabels::Yes => {} _ => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, retain_labels, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } } } else { let retain_labels: CharOffsetInLine = get_property_numeric(emacs, ":retain-labels")?.expect("Cannot be None or else the earlier get_property_unquoted_atom would have been None."); match (retain_labels, rust_value) { (e, RetainLabels::Keep(r)) if e == *r => {} _ => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, retain_labels, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } } } } else { match rust_value { RetainLabels::No => {} _ => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", emacs_field, retain_labels, rust_value )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } } } Ok(ComparePropertiesResult::NoChange) }