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.
This commit is contained in:
Tom Alexander 2023-12-29 11:37:30 -05:00
parent eff5cdbf40
commit 77e0dbb42e
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
3 changed files with 174 additions and 1 deletions

View File

@ -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.

View File

@ -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<WasmAstNode<'s, 'p>>,
#[serde(rename = "CATEGORY")]
pub(crate) category: Option<&'p str>,
pub(crate) path: Option<PathBuf>,
}

View File

@ -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<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
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<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
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<Token<'s>>,
wasm: &serde_json::Map<String, serde_json::Value>,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
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<String> = emacs_attributes_map
.keys()
.map(|s| (*s).to_owned())
.collect();
let wasm_keys: std::collections::BTreeSet<String> = 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::<String>(),
)
.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::<String>(),
)
.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<WK: std::fmt::Display>(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<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
wasm.compare_ast_node(source, emacs)
}