From 0aa746fb1e96b1b1705b62e1b63b8a17df40b6b6 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 16 Oct 2023 12:50:53 -0400 Subject: [PATCH] Implement comparison for object tree. --- src/compare/compare_field.rs | 138 +++++++++++++++++-------------- src/compare/util.rs | 25 +++--- src/parser/affiliated_keyword.rs | 38 --------- 3 files changed, 87 insertions(+), 114 deletions(-) diff --git a/src/compare/compare_field.rs b/src/compare/compare_field.rs index 3f662e20..47e6c9ed 100644 --- a/src/compare/compare_field.rs +++ b/src/compare/compare_field.rs @@ -16,7 +16,6 @@ use super::util::get_property_unquoted_atom; use crate::types::AstNode; use crate::types::CharOffsetInLine; use crate::types::LineNumber; -use crate::types::Object; use crate::types::RetainLabels; use crate::types::SwitchNumberLines; @@ -513,118 +512,133 @@ where Ok(ComparePropertiesResult::NoChange) } -/// Special compare used for affiliate keywords that are parsed as objects. -/// -/// Org-mode seems to store these as a 3-deep list: -/// - Outer list with 1 element per #+caption keyword (or other parsed keyword). -/// - Middle list which has: -/// - first element is a list of objects representing the value after the colon. -/// - every additional element is a list of objects from inside the square brackets (the optional value). -pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes< +pub(crate) fn compare_property_object_tree< 'b, 's, 'x, R, - RG: Fn(R) -> Option<&'b Vec<(Option>>, Vec>)>>, + 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> { - // TODO: Replace Object<'s> with generics. I hard-coded Object in to make lifetimes easier. - let rust_value = rust_value_getter(rust_node); +) -> 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 (value, rust_value) = match (value, rust_value) { + 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, Some(_)) | (Some(_), None) => { + (None, rv @ Some(_)) | (Some(_), rv @ None) => { let this_status = DiffStatus::Bad; let message = Some(format!( "{} mismatch (emacs != rust) {:?} != {:?}", - emacs_field, value, rust_value + emacs_field, value, rv )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } - (Some(value), Some(rust_value)) if value.len() != rust_value.len() => { + (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 + emacs_field, el, rl )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } - (Some(value), Some(rust_value)) => (value, rust_value), + (Some(el), Some(rl)) => (el, rl), }; + let mut full_status: Vec> = Vec::with_capacity(outer_rust_list.len()); - let mut full_status: Vec> = Vec::with_capacity(rust_value.len()); - - // Iterate the outer lists - for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) { - let mut middle_value = value.as_list()?.iter(); - // First element of middle list is the mandatory value (the value past the colon). - let mandatory_value = middle_value.next(); - let mandatory_value = match mandatory_value { - Some(mandatory_value) => mandatory_value, - None => { + for (kw_e, kw_r) in outer_emacs_list.into_iter().zip(outer_rust_list) { + 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.into_iter(); + // First element is a list representing the mandatory value. + if let Some(val_e) = kw_e.next() { + 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.into_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, value, rust_value + emacs_field, kw_e, kw_r )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } - }; - - // Compare optional value - if let Some(rust_optional) = rust_optional { - let mut child_status: Vec> = Vec::with_capacity(rust_value.len()); - if rust_optional.len() != middle_value.len() { + // Remaining elements are the optional value. + if kw_e.len() != or.len() { let this_status = DiffStatus::Bad; let message = Some(format!( - "{} optional value length mismatch (emacs != rust) {} != {} | {:?}", - emacs_field, - middle_value.len(), - rust_optional.len(), - rust_optional + "{} mismatch (emacs != rust) {:?} != {:?}", + emacs_field, kw_e, kw_r )); return Ok(ComparePropertiesResult::SelfChange(this_status, message)); } - for (e, r) in middle_value.zip(rust_optional) { + for (e, r) in kw_e.zip(or.iter()) { child_status.push(compare_ast_node(source, e, r.into())?); } - if !child_status.is_empty() { - let diff_scope = artificial_diff_scope("optional value", child_status)?; - full_status.push(diff_scope); + } 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)); } - } - // Compare mandatory value - let mut child_status: Vec> = Vec::with_capacity(rust_value.len()); - let mandatory_value = mandatory_value.as_list()?; - if rust_value.len() != mandatory_value.len() { - let this_status = DiffStatus::Bad; - let message = Some(format!( - "{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}", - emacs_field, - mandatory_value.len(), - rust_value.len(), - rust_value - )); - return Ok(ComparePropertiesResult::SelfChange(this_status, message)); - } - for (e, r) in mandatory_value.iter().zip(rust_value) { - child_status.push(compare_ast_node(source, e, r.into())?); + 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 { diff --git a/src/compare/util.rs b/src/compare/util.rs index f3b26c7b..c0a37609 100644 --- a/src/compare/util.rs +++ b/src/compare/util.rs @@ -1,7 +1,7 @@ use std::str::FromStr; -use super::compare_field::compare_property_list_of_list_of_list_of_ast_nodes; use super::compare_field::compare_property_list_of_quoted_string; +use super::compare_field::compare_property_object_tree; use super::compare_field::compare_property_optional_pair; use super::compare_field::compare_property_quoted_string; use super::compare_field::ComparePropertiesResult; @@ -386,20 +386,17 @@ where |_| Some((*optval, *val)), )?; ret.push(diff); - // todo } - AffiliatedKeywordValue::ObjectTree(_) => { - // todo - } // AffiliatedKeywordValue::ListOfListsOfObjects(rust_value) => { - // let diff = compare_property_list_of_list_of_list_of_ast_nodes( - // source, - // emacs, - // rust, - // emacs_property_name.as_str(), - // |_| Some(rust_value), - // )?; - // ret.push(diff); - // } + AffiliatedKeywordValue::ObjectTree(rust_value) => { + let diff = compare_property_object_tree( + source, + emacs, + rust, + emacs_property_name.as_str(), + |_| Some(rust_value.iter()), + )?; + ret.push(diff); + } }; } Ok(ret) diff --git a/src/parser/affiliated_keyword.rs b/src/parser/affiliated_keyword.rs index bc02edb1..f8dd9e7b 100644 --- a/src/parser/affiliated_keyword.rs +++ b/src/parser/affiliated_keyword.rs @@ -171,41 +171,3 @@ fn identify_keyword_type<'g, 's>( (false, false, false) => AffiliatedKeywordType::SingleString, } } - -fn is_single_string_keyword<'g, 's>( - _global_settings: &'g GlobalSettings<'g, 's>, - name: &'s str, -) -> bool { - // TODO: Is this defined by an elisp variable? - for single_string_name in ["plot", "name"] { - if name.eq_ignore_ascii_case(single_string_name) { - return true; - } - } - false -} - -fn is_list_of_single_string_keyword<'g, 's>( - _global_settings: &'g GlobalSettings<'g, 's>, - name: &'s str, -) -> bool { - // TODO: Is this defined by an elisp variable? - for single_string_name in ["results"] { - if name.eq_ignore_ascii_case(single_string_name) { - return true; - } - } - false -} - -fn is_list_of_objects_keyword<'g, 's>( - global_settings: &'g GlobalSettings<'g, 's>, - name: &'s str, -) -> bool { - for parsed_keyword in global_settings.element_parsed_keywords { - if name.eq_ignore_ascii_case(parsed_keyword) { - return true; - } - } - false -}