Merge branch 'compare_fields_macro'
This commit is contained in:
		
						commit
						e2bc58a469
					
				
							
								
								
									
										53
									
								
								src/compare/compare_field.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/compare/compare_field.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
 | 
			
		||||
use super::diff::DiffStatus;
 | 
			
		||||
use super::sexp::Token;
 | 
			
		||||
use super::util::get_property_quoted_string;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub(crate) enum EmacsField<'s> {
 | 
			
		||||
    Required(&'s str),
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    Optional(&'s str),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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.
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
 | 
			
		||||
    _emacs: &'b Token<'s>,
 | 
			
		||||
    _rust_node: R,
 | 
			
		||||
    _emacs_field: &'x str,
 | 
			
		||||
    _rust_value_getter: RG,
 | 
			
		||||
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
 | 
			
		||||
    Ok(None)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// 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.
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub(crate) fn compare_identity() -> () {
 | 
			
		||||
    ()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) fn compare_property_quoted_string<'b, 's, 'x, R, RG: Fn(R) -> Option<&'s str>>(
 | 
			
		||||
    emacs: &'b Token<'s>,
 | 
			
		||||
    rust_node: R,
 | 
			
		||||
    emacs_field: &'x str,
 | 
			
		||||
    rust_value_getter: RG,
 | 
			
		||||
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
 | 
			
		||||
    let value = get_property_quoted_string(emacs, emacs_field)?;
 | 
			
		||||
    let rust_value = rust_value_getter(rust_node);
 | 
			
		||||
    if !rust_value.eq(&value.as_ref().map(String::as_str)) {
 | 
			
		||||
        let this_status = DiffStatus::Bad;
 | 
			
		||||
        let message = Some(format!(
 | 
			
		||||
            "{} mismatch (emacs != rust) {:?} != {:?}",
 | 
			
		||||
            emacs_field, value, rust_value
 | 
			
		||||
        ));
 | 
			
		||||
        Ok(Some((this_status, message)))
 | 
			
		||||
    } else {
 | 
			
		||||
        Ok(None)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -3,6 +3,7 @@ use std::borrow::Cow;
 | 
			
		||||
use std::collections::BTreeSet;
 | 
			
		||||
use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
use super::compare_field::compare_property_quoted_string;
 | 
			
		||||
use super::elisp_fact::ElispFact;
 | 
			
		||||
use super::elisp_fact::GetElispFact;
 | 
			
		||||
use super::sexp::unquote;
 | 
			
		||||
@ -13,6 +14,8 @@ use super::util::get_property_boolean;
 | 
			
		||||
use super::util::get_property_numeric;
 | 
			
		||||
use super::util::get_property_quoted_string;
 | 
			
		||||
use super::util::get_property_unquoted_atom;
 | 
			
		||||
use crate::compare::compare_field::EmacsField;
 | 
			
		||||
use crate::compare::macros::compare_properties;
 | 
			
		||||
use crate::types::AngleLink;
 | 
			
		||||
use crate::types::AstNode;
 | 
			
		||||
use crate::types::BabelCall;
 | 
			
		||||
@ -124,7 +127,7 @@ pub struct DiffResult<'b, 's> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
enum DiffStatus {
 | 
			
		||||
pub(crate) enum DiffStatus {
 | 
			
		||||
    Good,
 | 
			
		||||
    Bad,
 | 
			
		||||
}
 | 
			
		||||
@ -2572,9 +2575,14 @@ fn compare_bold<'b, 's>(
 | 
			
		||||
    rust: &'b Bold<'s>,
 | 
			
		||||
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
 | 
			
		||||
    let children = emacs.as_list()?;
 | 
			
		||||
    let this_status = DiffStatus::Good;
 | 
			
		||||
    let mut this_status = DiffStatus::Good;
 | 
			
		||||
    let mut child_status = Vec::new();
 | 
			
		||||
    let message = None;
 | 
			
		||||
    let mut message = None;
 | 
			
		||||
 | 
			
		||||
    if let Some((new_status, new_message)) = compare_properties!(emacs)? {
 | 
			
		||||
        this_status = new_status;
 | 
			
		||||
        message = new_message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
 | 
			
		||||
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
 | 
			
		||||
@ -2597,9 +2605,14 @@ fn compare_italic<'b, 's>(
 | 
			
		||||
    rust: &'b Italic<'s>,
 | 
			
		||||
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
 | 
			
		||||
    let children = emacs.as_list()?;
 | 
			
		||||
    let this_status = DiffStatus::Good;
 | 
			
		||||
    let mut this_status = DiffStatus::Good;
 | 
			
		||||
    let mut child_status = Vec::new();
 | 
			
		||||
    let message = None;
 | 
			
		||||
    let mut message = None;
 | 
			
		||||
 | 
			
		||||
    if let Some((new_status, new_message)) = compare_properties!(emacs)? {
 | 
			
		||||
        this_status = new_status;
 | 
			
		||||
        message = new_message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
 | 
			
		||||
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
 | 
			
		||||
@ -2622,9 +2635,14 @@ fn compare_underline<'b, 's>(
 | 
			
		||||
    rust: &'b Underline<'s>,
 | 
			
		||||
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
 | 
			
		||||
    let children = emacs.as_list()?;
 | 
			
		||||
    let this_status = DiffStatus::Good;
 | 
			
		||||
    let mut this_status = DiffStatus::Good;
 | 
			
		||||
    let mut child_status = Vec::new();
 | 
			
		||||
    let message = None;
 | 
			
		||||
    let mut message = None;
 | 
			
		||||
 | 
			
		||||
    if let Some((new_status, new_message)) = compare_properties!(emacs)? {
 | 
			
		||||
        this_status = new_status;
 | 
			
		||||
        message = new_message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
 | 
			
		||||
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
 | 
			
		||||
@ -2649,14 +2667,17 @@ fn compare_verbatim<'b, 's>(
 | 
			
		||||
    let mut this_status = DiffStatus::Good;
 | 
			
		||||
    let mut message = None;
 | 
			
		||||
 | 
			
		||||
    // Compare value
 | 
			
		||||
    let value = get_property_quoted_string(emacs, ":value")?;
 | 
			
		||||
    if value.as_ref().map(String::as_str) != Some(rust.contents) {
 | 
			
		||||
        this_status = DiffStatus::Bad;
 | 
			
		||||
        message = Some(format!(
 | 
			
		||||
            "Value mismatch (emacs != rust) {:?} != {:?}",
 | 
			
		||||
            value, rust.contents
 | 
			
		||||
        ));
 | 
			
		||||
    if let Some((new_status, new_message)) = compare_properties!(
 | 
			
		||||
        emacs,
 | 
			
		||||
        rust,
 | 
			
		||||
        (
 | 
			
		||||
            EmacsField::Required(":value"),
 | 
			
		||||
            |r| Some(r.contents),
 | 
			
		||||
            compare_property_quoted_string
 | 
			
		||||
        )
 | 
			
		||||
    )? {
 | 
			
		||||
        this_status = new_status;
 | 
			
		||||
        message = new_message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(DiffResult {
 | 
			
		||||
@ -2678,14 +2699,17 @@ fn compare_code<'b, 's>(
 | 
			
		||||
    let mut this_status = DiffStatus::Good;
 | 
			
		||||
    let mut message = None;
 | 
			
		||||
 | 
			
		||||
    // Compare value
 | 
			
		||||
    let value = get_property_quoted_string(emacs, ":value")?;
 | 
			
		||||
    if value.as_ref().map(String::as_str) != Some(rust.contents) {
 | 
			
		||||
        this_status = DiffStatus::Bad;
 | 
			
		||||
        message = Some(format!(
 | 
			
		||||
            "Value mismatch (emacs != rust) {:?} != {:?}",
 | 
			
		||||
            value, rust.contents
 | 
			
		||||
        ));
 | 
			
		||||
    if let Some((new_status, new_message)) = compare_properties!(
 | 
			
		||||
        emacs,
 | 
			
		||||
        rust,
 | 
			
		||||
        (
 | 
			
		||||
            EmacsField::Required(":value"),
 | 
			
		||||
            |r| Some(r.contents),
 | 
			
		||||
            compare_property_quoted_string
 | 
			
		||||
        )
 | 
			
		||||
    )? {
 | 
			
		||||
        this_status = new_status;
 | 
			
		||||
        message = new_message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(DiffResult {
 | 
			
		||||
@ -2705,9 +2729,14 @@ fn compare_strike_through<'b, 's>(
 | 
			
		||||
    rust: &'b StrikeThrough<'s>,
 | 
			
		||||
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
 | 
			
		||||
    let children = emacs.as_list()?;
 | 
			
		||||
    let this_status = DiffStatus::Good;
 | 
			
		||||
    let mut this_status = DiffStatus::Good;
 | 
			
		||||
    let mut child_status = Vec::new();
 | 
			
		||||
    let message = None;
 | 
			
		||||
    let mut message = None;
 | 
			
		||||
 | 
			
		||||
    if let Some((new_status, new_message)) = compare_properties!(emacs)? {
 | 
			
		||||
        this_status = new_status;
 | 
			
		||||
        message = new_message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
 | 
			
		||||
        child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										155
									
								
								src/compare/macros.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/compare/macros.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,155 @@
 | 
			
		||||
/// Assert only the listed properties exist on the Emacs AST node and compare their values to the values on the rust AST node.
 | 
			
		||||
///
 | 
			
		||||
/// Example invocation:
 | 
			
		||||
/// ```
 | 
			
		||||
///     if let Some((new_status, new_message)) = compare_properties!(
 | 
			
		||||
///         emacs,
 | 
			
		||||
///         rust,
 | 
			
		||||
///         (
 | 
			
		||||
///             EmacsField::Required(":key"),
 | 
			
		||||
///             |r| Some(r.key),
 | 
			
		||||
///             compare_property_quoted_string
 | 
			
		||||
///         ),
 | 
			
		||||
///         (
 | 
			
		||||
///             EmacsField::Required(":value"),
 | 
			
		||||
///             |r| Some(r.contents),
 | 
			
		||||
///             compare_property_quoted_string
 | 
			
		||||
///         ),
 | 
			
		||||
///         (EmacsField::Required(":pre-blank"), compare_identity, compare_noop)
 | 
			
		||||
///     )? {
 | 
			
		||||
///         this_status = new_status;
 | 
			
		||||
///         message = new_message;
 | 
			
		||||
///     }
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// Or if the node has no properties aside from :standard-properties, we can still assert that the node has no unexpected properties:
 | 
			
		||||
/// ```
 | 
			
		||||
///     if let Some((new_status, new_message)) = compare_properties!(emacs)? {
 | 
			
		||||
///         this_status = new_status;
 | 
			
		||||
///         message = new_message;
 | 
			
		||||
///     }
 | 
			
		||||
/// ```
 | 
			
		||||
macro_rules! compare_properties {
 | 
			
		||||
    ($emacs:expr, $rust:expr, $(($emacs_field:expr, $rust_value_getter:expr, $compare_fn: expr)),+) => {
 | 
			
		||||
        {
 | 
			
		||||
            let mut this_status = DiffStatus::Good;
 | 
			
		||||
            let mut message: Option<String> = None;
 | 
			
		||||
            let children = $emacs.as_list()?;
 | 
			
		||||
            let attributes_child = children
 | 
			
		||||
            .iter()
 | 
			
		||||
            .nth(1)
 | 
			
		||||
            .ok_or("Should have an attributes child.")?;
 | 
			
		||||
            let attributes_map = attributes_child.as_map()?;
 | 
			
		||||
            let mut emacs_keys: BTreeSet<&str> = attributes_map.keys().map(|s| *s).collect();
 | 
			
		||||
            if emacs_keys.contains(":standard-properties") {
 | 
			
		||||
                emacs_keys.remove(":standard-properties");
 | 
			
		||||
            } else {
 | 
			
		||||
                this_status = DiffStatus::Bad;
 | 
			
		||||
                message = Some(format!(
 | 
			
		||||
                    "Emacs token lacks :standard-properties field.",
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
            $(
 | 
			
		||||
            match $emacs_field {
 | 
			
		||||
                EmacsField::Required(name) if emacs_keys.contains(name) => {
 | 
			
		||||
                    emacs_keys.remove(name);
 | 
			
		||||
                },
 | 
			
		||||
                EmacsField::Optional(name) if emacs_keys.contains(name) => {
 | 
			
		||||
                    emacs_keys.remove(name);
 | 
			
		||||
                },
 | 
			
		||||
                EmacsField::Required(name) => {
 | 
			
		||||
                    this_status = DiffStatus::Bad;
 | 
			
		||||
                    message = Some(format!(
 | 
			
		||||
                        "Emacs token lacks required field: {}",
 | 
			
		||||
                        name
 | 
			
		||||
                    ));
 | 
			
		||||
                },
 | 
			
		||||
                EmacsField::Optional(_name) => {},
 | 
			
		||||
            }
 | 
			
		||||
            )+
 | 
			
		||||
 | 
			
		||||
            if !emacs_keys.is_empty() {
 | 
			
		||||
                let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
 | 
			
		||||
                let unexpected_keys = unexpected_keys.join(", ");
 | 
			
		||||
                this_status = DiffStatus::Bad;
 | 
			
		||||
                message = Some(format!(
 | 
			
		||||
                    "Emacs token had extra field(s): {}",
 | 
			
		||||
                    unexpected_keys
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $(
 | 
			
		||||
            let emacs_name = match $emacs_field {
 | 
			
		||||
                EmacsField::Required(name) => {
 | 
			
		||||
                    name
 | 
			
		||||
                },
 | 
			
		||||
                EmacsField::Optional(name) => {
 | 
			
		||||
                    name
 | 
			
		||||
                },
 | 
			
		||||
            };
 | 
			
		||||
            let result = $compare_fn($emacs, $rust, emacs_name, $rust_value_getter)?;
 | 
			
		||||
            match result {
 | 
			
		||||
                Some((DiffStatus::Good, _)) => unreachable!("No comparison functions should return Some() when DiffStatus is good."),
 | 
			
		||||
                Some((status, msg)) => {
 | 
			
		||||
                    this_status = status;
 | 
			
		||||
                    message = msg;
 | 
			
		||||
                },
 | 
			
		||||
                _ => {}
 | 
			
		||||
            }
 | 
			
		||||
            )+
 | 
			
		||||
 | 
			
		||||
            match this_status {
 | 
			
		||||
                DiffStatus::Good => {
 | 
			
		||||
                    let result: Result<_, Box<dyn std::error::Error>> = Ok(None);
 | 
			
		||||
                    result
 | 
			
		||||
                },
 | 
			
		||||
                _ => {
 | 
			
		||||
                    Ok(Some((this_status, message)))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    // Default case for when there are no expected properties except for :standard-properties
 | 
			
		||||
    ($emacs:expr) => {
 | 
			
		||||
        {
 | 
			
		||||
            let mut this_status = DiffStatus::Good;
 | 
			
		||||
            let mut message: Option<String> = None;
 | 
			
		||||
            let children = $emacs.as_list()?;
 | 
			
		||||
            let attributes_child = children
 | 
			
		||||
            .iter()
 | 
			
		||||
            .nth(1)
 | 
			
		||||
            .ok_or("Should have an attributes child.")?;
 | 
			
		||||
            let attributes_map = attributes_child.as_map()?;
 | 
			
		||||
            let mut emacs_keys: BTreeSet<&str> = attributes_map.keys().map(|s| *s).collect();
 | 
			
		||||
            if emacs_keys.contains(":standard-properties") {
 | 
			
		||||
                emacs_keys.remove(":standard-properties");
 | 
			
		||||
            } else {
 | 
			
		||||
                this_status = DiffStatus::Bad;
 | 
			
		||||
                message = Some(format!(
 | 
			
		||||
                    "Emacs token lacks :standard-properties field.",
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if !emacs_keys.is_empty() {
 | 
			
		||||
                let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
 | 
			
		||||
                let unexpected_keys = unexpected_keys.join(", ");
 | 
			
		||||
                this_status = DiffStatus::Bad;
 | 
			
		||||
                message = Some(format!(
 | 
			
		||||
                    "Emacs token had extra field(s): {}",
 | 
			
		||||
                    unexpected_keys
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
            match this_status {
 | 
			
		||||
                DiffStatus::Good => {
 | 
			
		||||
                    let result: Result<_, Box<dyn std::error::Error>> = Ok(None);
 | 
			
		||||
                    result
 | 
			
		||||
                },
 | 
			
		||||
                _ => {
 | 
			
		||||
                    Ok(Some((this_status, message)))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) use compare_properties;
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
mod compare;
 | 
			
		||||
mod compare_field;
 | 
			
		||||
mod diff;
 | 
			
		||||
mod elisp_fact;
 | 
			
		||||
mod macros;
 | 
			
		||||
mod parse;
 | 
			
		||||
mod sexp;
 | 
			
		||||
mod util;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user