From 77e0dbb42e815bd02ac2b36372c554bb00f69911 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Fri, 29 Dec 2023 11:37:30 -0500 Subject: [PATCH] Start working on a version of compare based on json values. This will be a better test because it will be testing that what we export to json is equivalent to the elisp AST generated from emacs. Because of these tests, we could also confidently use the wasm structure to elisp. --- src/lib.rs | 1 + src/wasm/document.rs | 5 +- src/wasm_test/compare.rs | 169 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 1 deletion(-) 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) }