Implement comparison for object tree.

This commit is contained in:
Tom Alexander 2023-10-16 12:50:53 -04:00
parent 33800c4a88
commit 0aa746fb1e
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
3 changed files with 87 additions and 114 deletions

View File

@ -16,7 +16,6 @@ use super::util::get_property_unquoted_atom;
use crate::types::AstNode; use crate::types::AstNode;
use crate::types::CharOffsetInLine; use crate::types::CharOffsetInLine;
use crate::types::LineNumber; use crate::types::LineNumber;
use crate::types::Object;
use crate::types::RetainLabels; use crate::types::RetainLabels;
use crate::types::SwitchNumberLines; use crate::types::SwitchNumberLines;
@ -513,118 +512,133 @@ where
Ok(ComparePropertiesResult::NoChange) Ok(ComparePropertiesResult::NoChange)
} }
/// Special compare used for affiliate keywords that are parsed as objects. pub(crate) fn compare_property_object_tree<
///
/// 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<
'b, 'b,
's, 's,
'x, 'x,
R, R,
RG: Fn(R) -> Option<&'b Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>>, RV: std::fmt::Debug + 'b,
ROV: std::fmt::Debug + 'b,
RI: Iterator<Item = &'b (Option<Vec<ROV>>, Vec<RV>)> + ExactSizeIterator + std::fmt::Debug,
RG: Fn(R) -> Option<RI>,
>( >(
source: &'s str, source: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust_node: R, rust_node: R,
emacs_field: &'x str, emacs_field: &'x str,
rust_value_getter: RG, rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> { ) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>>
// TODO: Replace Object<'s> with generics. I hard-coded Object in to make lifetimes easier. where
let rust_value = rust_value_getter(rust_node); AstNode<'b, 's>: From<&'b RV>,
AstNode<'b, 's>: From<&'b ROV>,
{
let value = get_property(emacs, emacs_field)? let value = get_property(emacs, emacs_field)?
.map(Token::as_list) .map(Token::as_list)
.map_or(Ok(None), |r| r.map(Some))?; .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) => { (None, None) => {
return Ok(ComparePropertiesResult::NoChange); return Ok(ComparePropertiesResult::NoChange);
} }
(None, Some(_)) | (Some(_), None) => { (None, rv @ Some(_)) | (Some(_), rv @ None) => {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}", "{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value emacs_field, value, rv
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); 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 this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}", "{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value emacs_field, el, rl
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); 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<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len());
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len()); for (kw_e, kw_r) in outer_emacs_list.into_iter().zip(outer_rust_list) {
let kw_e = kw_e.as_list()?;
// Iterate the outer lists let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) { let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
let mut middle_value = value.as_list()?.iter(); if let Some(or) = &kw_r.0 {
// First element of middle list is the mandatory value (the value past the colon). // if optional value
let mandatory_value = middle_value.next(); let mut kw_e = kw_e.into_iter();
let mandatory_value = match mandatory_value { // First element is a list representing the mandatory value.
Some(mandatory_value) => mandatory_value, if let Some(val_e) = kw_e.next() {
None => { 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 this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}", "{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value emacs_field, kw_e, kw_r
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); return Ok(ComparePropertiesResult::SelfChange(this_status, message));
} }
}; // Remaining elements are the optional value.
if kw_e.len() != or.len() {
// Compare optional value
if let Some(rust_optional) = rust_optional {
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
if rust_optional.len() != middle_value.len() {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
"{} optional value length mismatch (emacs != rust) {} != {} | {:?}", "{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, emacs_field, kw_e, kw_r
middle_value.len(),
rust_optional.len(),
rust_optional
)); ));
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); 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())?); child_status.push(compare_ast_node(source, e, r.into())?);
} }
if !child_status.is_empty() { } else {
let diff_scope = artificial_diff_scope("optional value", child_status)?; // if no optional value
full_status.push(diff_scope); 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 e = kw_e
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len()); .first()
let mandatory_value = mandatory_value.as_list()?; .map(Token::as_list)
if rust_value.len() != mandatory_value.len() { .map_or(Ok(None), |r| r.map(Some))?
let this_status = DiffStatus::Bad; .expect("The above if-statement proves this will be Some.")
let message = Some(format!( .iter();
"{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}", let r = kw_r.1.iter();
emacs_field,
mandatory_value.len(), if e.len() != r.len() {
rust_value.len(), let this_status = DiffStatus::Bad;
rust_value let message = Some(format!(
)); "{} mismatch (emacs != rust) {:?} != {:?}",
return Ok(ComparePropertiesResult::SelfChange(this_status, message)); emacs_field, kw_e, kw_r
} ));
for (e, r) in mandatory_value.iter().zip(rust_value) { return Ok(ComparePropertiesResult::SelfChange(this_status, message));
child_status.push(compare_ast_node(source, e, r.into())?); }
for (e, r) in e.zip(r) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
} }
if !child_status.is_empty() { if !child_status.is_empty() {
let diff_scope = artificial_diff_scope("mandatory value", child_status)?; let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
full_status.push(diff_scope); full_status.push(diff_scope);
} }
} }
if full_status.is_empty() { if full_status.is_empty() {
Ok(ComparePropertiesResult::NoChange) Ok(ComparePropertiesResult::NoChange)
} else { } else {

View File

@ -1,7 +1,7 @@
use std::str::FromStr; 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_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_optional_pair;
use super::compare_field::compare_property_quoted_string; use super::compare_field::compare_property_quoted_string;
use super::compare_field::ComparePropertiesResult; use super::compare_field::ComparePropertiesResult;
@ -386,20 +386,17 @@ where
|_| Some((*optval, *val)), |_| Some((*optval, *val)),
)?; )?;
ret.push(diff); ret.push(diff);
// todo
} }
AffiliatedKeywordValue::ObjectTree(_) => { AffiliatedKeywordValue::ObjectTree(rust_value) => {
// todo let diff = compare_property_object_tree(
} // AffiliatedKeywordValue::ListOfListsOfObjects(rust_value) => { source,
// let diff = compare_property_list_of_list_of_list_of_ast_nodes( emacs,
// source, rust,
// emacs, emacs_property_name.as_str(),
// rust, |_| Some(rust_value.iter()),
// emacs_property_name.as_str(), )?;
// |_| Some(rust_value), ret.push(diff);
// )?; }
// ret.push(diff);
// }
}; };
} }
Ok(ret) Ok(ret)

View File

@ -171,41 +171,3 @@ fn identify_keyword_type<'g, 's>(
(false, false, false) => AffiliatedKeywordType::SingleString, (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
}