macro_rules! wasm_compare { ($source:expr, $emacs:expr, $wasm:expr, $(($emacs_field:expr, $wasm_value_getter:expr, $compare_fn: expr)),*) => {{ let mut result = WasmDiffResult::default(); let emacs_list = $emacs.as_list()?; let mut emacs_list_iter = emacs_list.iter(); let emacs_name = emacs_list_iter .next() .ok_or("Should have an attributes child.")? .as_atom()?; let emacs_attributes_map = emacs_list_iter .next() .ok_or("Should have an attributes child.")? .as_map()?; let mut emacs_keys: std::collections::BTreeSet<&str> = emacs_attributes_map.keys().map(|s| *s).collect(); { // Compare name let wasm_name = $wasm.get_elisp_name(); if emacs_name != wasm_name { result.status.push(WasmDiffStatus::Bad( format!( "AST node name mismatch. Emacs=({emacs}) Wasm=({wasm}).", emacs = emacs_name, wasm = wasm_name, ) .into(), )); } } { // Compare children result.extend(wasm_compare_list( $source, emacs_list_iter, $wasm.children.iter(), )?)?; } { // Compare fields $( 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) => { result.status.push(WasmDiffStatus::Bad( format!( "Emacs node lacked required field ({name}).", name = name, ) .into(), )); } EmacsField::Optional(_name) => {} } )* } result }}; } pub(crate) use wasm_compare;