diff --git a/src/lib.rs b/src/lib.rs index 3a791a2..4924228 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![feature(path_file_prefix)] #![feature(is_sorted)] #![feature(test)] +#![feature(iter_intersperse)] // TODO: #![warn(missing_docs)] #![allow(clippy::bool_assert_comparison)] // Sometimes you want the long form because its easier to see at a glance. diff --git a/src/wasm/document.rs b/src/wasm/document.rs index df49157..9e812c7 100644 --- a/src/wasm/document.rs +++ b/src/wasm/document.rs @@ -14,13 +14,16 @@ use crate::types::Document; use crate::wasm::to_wasm::ToWasmStandardProperties; #[derive(Debug, Serialize)] -#[serde(tag = "ast_node")] +#[serde(tag = "__ast_node")] #[serde(rename = "org-data")] pub struct WasmDocument<'s, 'p> { + #[serde(rename = "standard-properties")] pub(crate) standard_properties: WasmStandardProperties, #[serde(flatten)] pub(crate) additional_properties: AdditionalProperties<'s, 'p>, + #[serde(rename = "__children")] pub(crate) children: Vec>, + #[serde(rename = "CATEGORY")] pub(crate) category: Option<&'p str>, pub(crate) path: Option, } diff --git a/src/wasm_test/compare.rs b/src/wasm_test/compare.rs index dc5d67b..a5033fd 100644 --- a/src/wasm_test/compare.rs +++ b/src/wasm_test/compare.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use super::diff::WasmDiffResult; use super::diff::WasmDiffStatus; use super::elisp_compare::WasmElispCompare; @@ -73,6 +75,173 @@ pub fn wasm_compare_document<'b, 's, 'p>( source: &'s str, emacs: &'b Token<'s>, wasm: WasmDocument<'s, 'p>, +) -> Result, Box> { + let wasm_json = serde_json::to_string(&wasm)?; + let wasm_json_parsed = serde_json::from_str(&wasm_json)?; + compare_json_value(&wasm_json_parsed, source, emacs) +} + +fn compare_json_value<'b, 's>( + value: &serde_json::Value, + source: &'s str, + emacs: &'b Token<'s>, +) -> Result, Box> { + match (value, emacs) { + (serde_json::Value::Object(wasm), Token::List(el)) if wasm.contains_key("__ast_node") => { + // We hit a regular ast node. + compare_ast_node(source, el, wasm) + } + (serde_json::Value::Null, Token::Atom(_)) => todo!(), + (serde_json::Value::Null, Token::List(_)) => todo!(), + (serde_json::Value::Null, Token::TextWithProperties(_)) => todo!(), + (serde_json::Value::Null, Token::Vector(_)) => todo!(), + (serde_json::Value::Bool(_), Token::Atom(_)) => todo!(), + (serde_json::Value::Bool(_), Token::List(_)) => todo!(), + (serde_json::Value::Bool(_), Token::TextWithProperties(_)) => todo!(), + (serde_json::Value::Bool(_), Token::Vector(_)) => todo!(), + (serde_json::Value::Number(_), Token::Atom(_)) => todo!(), + (serde_json::Value::Number(_), Token::List(_)) => todo!(), + (serde_json::Value::Number(_), Token::TextWithProperties(_)) => todo!(), + (serde_json::Value::Number(_), Token::Vector(_)) => todo!(), + (serde_json::Value::String(_), Token::Atom(_)) => todo!(), + (serde_json::Value::String(_), Token::List(_)) => todo!(), + (serde_json::Value::String(_), Token::TextWithProperties(_)) => todo!(), + (serde_json::Value::String(_), Token::Vector(_)) => todo!(), + (serde_json::Value::Array(_), Token::Atom(_)) => todo!(), + (serde_json::Value::Array(_), Token::List(_)) => todo!(), + (serde_json::Value::Array(_), Token::TextWithProperties(_)) => todo!(), + (serde_json::Value::Array(_), Token::Vector(_)) => todo!(), + (serde_json::Value::Object(_), Token::Atom(_)) => todo!(), + (serde_json::Value::Object(_), Token::List(_)) => todo!(), + (serde_json::Value::Object(_), Token::TextWithProperties(_)) => todo!(), + (serde_json::Value::Object(_), Token::Vector(_)) => todo!(), + } +} + +fn compare_ast_node<'b, 's, 'p>( + source: &'s str, + emacs: &'b Vec>, + wasm: &serde_json::Map, +) -> Result, Box> { + let mut result = WasmDiffResult::default(); + let mut emacs_list_iter = emacs.iter(); + + { + // Compare ast node type. + let emacs_name = emacs_list_iter + .next() + .ok_or("Should have a name as the first child.")? + .as_atom()?; + let wasm_name = wasm + .get("__ast_node") + .ok_or("Should have a ast node type.")? + .as_str() + .ok_or("Ast node type should be a string.")?; + result.name = emacs_name.into(); + 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(), + )); + } + } + + if result.is_bad() { + return Ok(result); + } + + let emacs_attributes_map = emacs_list_iter + .next() + .ok_or("Should have an attributes child.")? + .as_map()?; + + { + // Compare attribute names. + let emacs_keys: std::collections::BTreeSet = emacs_attributes_map + .keys() + .map(|s| (*s).to_owned()) + .collect(); + let wasm_keys: std::collections::BTreeSet = wasm + .keys() + .filter(|k| !k.starts_with("__")) + .map(wasm_key_to_emacs_key) + .collect(); + let emacs_only_attributes: Vec<&String> = emacs_keys.difference(&wasm_keys).collect(); + let wasm_only_attributes: Vec<&String> = wasm_keys.difference(&emacs_keys).collect(); + if !emacs_only_attributes.is_empty() { + result.status.push(WasmDiffStatus::Bad( + format!( + "Wasm node lacked field present in elisp node ({name}).", + name = emacs_only_attributes + .iter() + .map(|s| s.as_str()) + .intersperse(", ") + .collect::(), + ) + .into(), + )); + } + if !wasm_only_attributes.is_empty() { + result.status.push(WasmDiffStatus::Bad( + format!( + "Elisp node lacked field present in wasm node ({name}).", + name = wasm_only_attributes + .iter() + .map(|s| s.as_str()) + .intersperse(", ") + .collect::(), + ) + .into(), + )); + } + } + + if result.is_bad() { + return Ok(result); + } + + { + // Compare attributes. + for attribute_name in wasm + .keys() + .filter(|k| !k.starts_with("__") && k.as_str() != "standard-properties") + { + let mut layer = WasmDiffResult::default(); + layer.name = Cow::Owned(attribute_name.clone()); + let wasm_attribute_value = wasm + .get(attribute_name) + .ok_or("Key should exist in both wasm and elisp at this point.")?; + let emacs_key = wasm_key_to_emacs_key(attribute_name); + let emacs_attribute_value = *emacs_attributes_map + .get(emacs_key.as_str()) + .ok_or("Key should exist in both wasm and elisp at this point.")?; + layer.extend(compare_json_value( + wasm_attribute_value, + source, + emacs_attribute_value, + )?)?; + result.children.push(layer); + } + } + + // TODO: compare standard-properties. + // TODO: compare children + + Ok(result) +} + +fn wasm_key_to_emacs_key(wasm_key: WK) -> String { + format!(":{key}", key = wasm_key) +} + +pub fn old_wasm_compare_document<'b, 's, 'p>( + source: &'s str, + emacs: &'b Token<'s>, + wasm: WasmDocument<'s, 'p>, ) -> Result, Box> { wasm.compare_ast_node(source, emacs) }