organic/src/wasm_test/compare.rs

893 lines
33 KiB
Rust
Raw Normal View History

2023-12-30 23:25:15 +00:00
use std::borrow::Borrow;
use std::borrow::Cow;
2023-12-30 23:25:15 +00:00
use std::collections::BTreeSet;
2023-12-29 20:38:18 +00:00
use std::collections::HashMap;
use super::diff::WasmDiffResult;
use super::diff::WasmDiffStatus;
2023-12-29 20:38:18 +00:00
use crate::compare::maybe_token_to_usize;
2023-12-29 22:24:38 +00:00
use crate::compare::unquote;
2023-12-29 20:38:18 +00:00
use crate::compare::EmacsStandardProperties;
2023-12-29 22:24:38 +00:00
use crate::compare::TextWithProperties;
2023-12-27 14:31:54 +00:00
use crate::compare::Token;
use crate::wasm::WasmAstNodeWrapper;
2023-12-27 14:31:54 +00:00
use crate::wasm::WasmDocument;
2023-12-29 17:49:43 +00:00
pub fn wasm_compare_document<'e, 's, 'w>(
2023-12-27 16:10:40 +00:00
source: &'s str,
2023-12-29 17:49:43 +00:00
emacs: &'e Token<'s>,
wasm: &'w WasmAstNodeWrapper<WasmDocument>,
) -> 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)?;
2023-12-29 21:56:02 +00:00
compare_json_value(source, emacs, &wasm_json_parsed)
}
fn compare_json_value<'b, 's>(
source: &'s str,
emacs: &'b Token<'s>,
2023-12-29 21:56:02 +00:00
wasm: &serde_json::Value,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
2023-12-29 22:46:35 +00:00
// println!("XXXXXXXXXXXXXX compare_json_value XXXXXXXXXXXXXX");
// println!("{:?}", emacs);
// println!("{:?}", wasm);
// println!("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
2023-12-29 21:56:02 +00:00
match (wasm, 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::String(w), Token::Atom(e))
if e.starts_with('"') && e.ends_with('"') =>
{
// We hit a string compared against a quoted string from elisp (as opposed to an unquoted literal).
compare_quoted_string(source, e, w)
}
2023-12-29 21:51:52 +00:00
(serde_json::Value::Array(w), Token::List(e)) => {
2023-12-29 22:46:35 +00:00
// TODO: This is creating children with no names.
2023-12-29 21:51:52 +00:00
wasm_compare_list(source, e.iter(), w.iter())
}
(serde_json::Value::Object(wasm), Token::List(e))
if wasm.contains_key("optval") && wasm.contains_key("val") =>
{
compare_optional_pair(source, e, wasm)
}
2023-12-29 22:46:35 +00:00
(serde_json::Value::Object(wasm), Token::List(el)) if wasm.contains_key("object-tree") => {
// We hit an object tree additional property.
compare_object_tree(source, el, wasm)
}
2023-12-30 22:09:12 +00:00
(serde_json::Value::Object(wasm), Token::List(el)) if wasm.contains_key("number-lines") => {
// We hit an object tree additional property.
compare_number_lines(source, el, wasm)
}
2023-12-30 23:25:15 +00:00
(serde_json::Value::Object(wasm), Token::List(el)) if wasm.contains_key("string-set") => {
// We hit an object tree additional property.
compare_string_set(source, el, wasm)
}
2023-12-29 22:24:38 +00:00
(serde_json::Value::Object(w), Token::TextWithProperties(e)) if is_plain_text(w) => {
compare_plain_text(source, e, w)
}
2023-12-30 00:21:35 +00:00
(serde_json::Value::Null, Token::Atom("nil")) => Ok(WasmDiffResult::default()),
2023-12-30 01:12:45 +00:00
(serde_json::Value::Bool(false), Token::Atom("nil")) => Ok(WasmDiffResult::default()),
(serde_json::Value::Bool(true), Token::Atom(e)) if (*e) != "nil" => {
Ok(WasmDiffResult::default())
}
(serde_json::Value::Bool(w), Token::Atom(e)) => {
let mut result = WasmDiffResult::default();
result.status.push(WasmDiffStatus::Bad(
format!(
"Value mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = e,
wasm = w,
)
.into(),
));
Ok(result)
}
(serde_json::Value::Number(w), Token::Atom(e)) if w.to_string().as_str() == (*e) => {
Ok(WasmDiffResult::default())
}
(serde_json::Value::Number(w), Token::Atom(e)) => {
let mut result = WasmDiffResult::default();
result.status.push(WasmDiffStatus::Bad(
format!(
"Value mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = e,
wasm = w,
)
.into(),
));
Ok(result)
}
2023-12-30 01:12:45 +00:00
(serde_json::Value::Array(w), Token::Atom("nil")) if w.is_empty() => {
Ok(WasmDiffResult::default())
}
(serde_json::Value::String(w), Token::Atom(e)) if w.as_str() == *e => {
Ok(WasmDiffResult::default())
}
2023-12-31 01:50:28 +00:00
(serde_json::Value::Object(w), _) if w.contains_key("noop") => {
Ok(WasmDiffResult::default())
}
(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!(),
2023-12-30 01:12:45 +00:00
// (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!(),
2023-12-30 01:12:45 +00:00
// (serde_json::Value::String(_), Token::Atom(_)) => todo!(),
(serde_json::Value::String(w), Token::Atom(e)) => {
let mut result = WasmDiffResult::default();
result.status.push(WasmDiffStatus::Bad(
format!(
"Value mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = e,
wasm = w,
)
.into(),
));
Ok(result)
}
(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!(),
2023-12-29 21:51:52 +00:00
// (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!(),
}
}
2023-12-30 01:12:45 +00:00
fn compare_optional_json_value<'b, 's>(
source: &'s str,
emacs: Option<&'b Token<'s>>,
wasm: Option<&serde_json::Value>,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
match (emacs, wasm) {
(None, None) | (None, Some(serde_json::Value::Null)) | (Some(Token::Atom("nil")), None) => {
Ok(WasmDiffResult::default())
}
2023-12-31 01:47:17 +00:00
(None, Some(serde_json::Value::Object(w))) if w.contains_key("noop") => {
Ok(WasmDiffResult::default())
}
2023-12-30 01:12:45 +00:00
(Some(e), Some(w)) => compare_json_value(source, e, w),
_ => Ok(WasmDiffResult {
status: vec![WasmDiffStatus::Bad(
format!(
"Nullness mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs,
wasm = wasm
)
.into(),
)],
children: Vec::new(),
name: "".into(),
}),
}
}
2023-12-29 21:06:07 +00:00
fn compare_ast_node<'e, 's, 'w>(
source: &'s str,
2023-12-29 21:06:07 +00:00
emacs: &'e Vec<Token<'s>>,
wasm: &'w 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()?;
let wasm_attributes_map = wasm
.get("properties")
.ok_or(r#"Wasm ast node should have a "properties" attribute."#)?
.as_object()
.ok_or(r#"Wasm ast node "properties" attribute should be an object."#)?;
{
// Compare attribute names.
let emacs_keys: std::collections::BTreeSet<String> = emacs_attributes_map
.keys()
.map(|s| (*s).to_owned())
.collect();
2023-12-30 01:12:45 +00:00
// wasm_attributes_map.iter().filter_map(|(k,v)| if matches!(v, serde_json::Value::Null) {None} else {Some(k)}).map(wasm_key_to_emacs_key)
let wasm_keys: std::collections::BTreeSet<String> =
std::iter::once(":standard-properties".to_owned())
.chain(wasm_attributes_map.keys().map(wasm_key_to_emacs_key))
.collect();
let emacs_only_attributes: Vec<&String> = emacs_keys.difference(&wasm_keys).collect();
2023-12-30 01:12:45 +00:00
let wasm_only_attributes: Vec<&String> = wasm_keys
.difference(&emacs_keys)
.filter(|attribute| {
emacs_attributes_map
.get(attribute.as_str())
.map(|token| !matches!(token, Token::Atom("nil")))
.unwrap_or(false)
})
.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_attributes_map.keys() {
let mut layer = WasmDiffResult::default();
layer.name = Cow::Owned(attribute_name.clone());
2023-12-30 01:12:45 +00:00
let wasm_attribute_value = wasm_attributes_map.get(attribute_name);
let emacs_key = wasm_key_to_emacs_key(attribute_name);
2023-12-30 01:12:45 +00:00
let emacs_attribute_value = emacs_attributes_map.get(emacs_key.as_str()).map(|e| *e);
2023-12-30 02:27:05 +00:00
let inner_layer =
compare_optional_json_value(source, emacs_attribute_value, wasm_attribute_value)?;
if !inner_layer.name.is_empty() {
layer.children.push(inner_layer);
} else {
layer.extend(inner_layer)?;
}
result.children.push(layer);
}
}
2023-12-29 20:38:18 +00:00
{
// Compare standard-properties.
let mut layer = WasmDiffResult::default();
layer.name = "standard-properties".into();
let emacs_standard_properties = wasm_get_emacs_standard_properties(&emacs_attributes_map)?;
let wasm_standard_properties = wasm
.get("standard-properties")
.ok_or(r#"Wasm AST nodes should have a "standard-properties" attribute."#)?
.as_object()
.ok_or(r#"Wasm ast node "standard-properties" attribute should be an object."#)?;
for (emacs_value, wasm_name) in [
(emacs_standard_properties.begin, "begin"),
(emacs_standard_properties.end, "end"),
(emacs_standard_properties.contents_begin, "contents-begin"),
(emacs_standard_properties.contents_end, "contents-end"),
(emacs_standard_properties.post_blank, "post-blank"),
2023-12-29 20:38:18 +00:00
] {
match (emacs_value, wasm_standard_properties.get(wasm_name)) {
2023-12-30 00:21:35 +00:00
(None, None) | (None, Some(serde_json::Value::Null)) => {}
2023-12-29 20:38:18 +00:00
(None, Some(_)) => {
layer.status.push(WasmDiffStatus::Bad(
format!(
"Elisp node lacked field present in wasm node. Name=({name}).",
name = wasm_name,
)
.into(),
));
}
(Some(_), None) => {
layer.status.push(WasmDiffStatus::Bad(
format!(
"Wasm node lacked field present in elisp node. Name=({name}).",
name = wasm_name,
)
.into(),
));
}
(Some(e), Some(serde_json::Value::Number(w)))
if w.as_u64().map(|w| w as usize) == Some(e) => {}
(Some(e), Some(w)) => {
layer.status.push(WasmDiffStatus::Bad(
format!(
"Property value mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = e,
wasm = w,
)
.into(),
));
}
}
}
result.children.push(layer);
}
2023-12-29 21:06:07 +00:00
{
// Compare children.
let mut layer = WasmDiffResult::default();
2023-12-29 22:26:28 +00:00
layer.name = "children".into();
2023-12-29 21:06:07 +00:00
if let Some(wasm_iter) = wasm
.get("children")
.map(|children| children.as_array())
.flatten()
.map(|children| children.iter())
{
layer.extend(wasm_compare_list(source, emacs_list_iter, wasm_iter)?)?;
} else {
layer.extend(wasm_compare_list(
source,
emacs_list_iter,
std::iter::empty::<&serde_json::Value>(),
)?)?;
}
result.children.push(layer);
}
Ok(result)
}
fn wasm_key_to_emacs_key<WK: std::fmt::Display>(wasm_key: WK) -> String {
format!(":{key}", key = wasm_key)
}
fn compare_quoted_string<'e, 's, 'w>(
source: &'s str,
emacs: &'e str,
wasm: &'w String,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
let mut result = WasmDiffResult::default();
2023-12-29 22:26:28 +00:00
let emacs_text = unquote(emacs)?;
if wasm.as_str() != emacs_text {
result.status.push(WasmDiffStatus::Bad(
format!(
"Text mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_text,
wasm = wasm,
)
.into(),
));
}
Ok(result)
}
2023-12-29 20:38:18 +00:00
pub(crate) fn wasm_get_emacs_standard_properties(
attributes_map: &HashMap<&str, &Token<'_>>,
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
let standard_properties = attributes_map.get(":standard-properties");
Ok(if standard_properties.is_some() {
let mut std_props = standard_properties
.expect("if statement proves its Some")
.as_vector()?
.iter();
let begin = maybe_token_to_usize(std_props.next())?;
let post_affiliated = maybe_token_to_usize(std_props.next())?;
let contents_begin = maybe_token_to_usize(std_props.next())?;
let contents_end = maybe_token_to_usize(std_props.next())?;
let end = maybe_token_to_usize(std_props.next())?;
let post_blank = maybe_token_to_usize(std_props.next())?;
EmacsStandardProperties {
begin,
post_affiliated,
contents_begin,
contents_end,
end,
post_blank,
}
} else {
let begin = maybe_token_to_usize(attributes_map.get(":begin").copied())?;
let end = maybe_token_to_usize(attributes_map.get(":end").copied())?;
let contents_begin = maybe_token_to_usize(attributes_map.get(":contents-begin").copied())?;
let contents_end = maybe_token_to_usize(attributes_map.get(":contents-end").copied())?;
let post_blank = maybe_token_to_usize(attributes_map.get(":post-blank").copied())?;
let post_affiliated =
maybe_token_to_usize(attributes_map.get(":post-affiliated").copied())?;
EmacsStandardProperties {
begin,
post_affiliated,
contents_begin,
contents_end,
end,
post_blank,
}
})
}
2023-12-29 21:06:07 +00:00
fn wasm_compare_list<'e, 's: 'e, 'w, EI, WI>(
source: &'s str,
mut emacs: EI,
2023-12-29 21:06:07 +00:00
wasm: WI,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>>
where
EI: Iterator<Item = &'e Token<'s>> + ExactSizeIterator,
WI: Iterator<Item = &'w serde_json::Value> + ExactSizeIterator,
{
let emacs_length = emacs.len();
let wasm_length = wasm.len();
if emacs_length == 1 && wasm_length == 0 {
if emacs.all(|t| matches!(t.as_atom(), Ok(r#""""#))) {
return Ok(WasmDiffResult::default());
}
}
2023-12-29 21:06:07 +00:00
if emacs_length != wasm_length {
return Ok(WasmDiffResult {
status: vec![WasmDiffStatus::Bad(
format!(
"Child length mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_length,
wasm = wasm_length
)
.into(),
)],
children: Vec::new(),
name: "".into(),
});
}
let mut child_status = Vec::with_capacity(emacs_length);
for (emacs_child, wasm_child) in emacs.zip(wasm) {
2023-12-29 21:56:02 +00:00
child_status.push(compare_json_value(source, emacs_child, wasm_child)?);
2023-12-29 21:06:07 +00:00
}
Ok(WasmDiffResult {
status: Vec::new(),
children: child_status,
name: "".into(),
})
}
2023-12-29 21:51:52 +00:00
fn compare_optional_pair<'e, 's, 'w>(
source: &'s str,
emacs: &'e Vec<Token<'s>>,
wasm: &'w serde_json::Map<String, serde_json::Value>,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
let mut result = WasmDiffResult::default();
let wasm_optval = wasm
.get("optval")
.ok_or(r#"Wasm optional pair should have an "optval" attribute."#)?;
let wasm_val = wasm
.get("val")
.ok_or(r#"Wasm optional pair should have an "optval" attribute."#)?;
if let serde_json::Value::Null = wasm_optval {
// If the optval is null, then the elisp should have just a single value of a quoted string.
if emacs.len() != 1 {
return Ok(WasmDiffResult {
status: vec![WasmDiffStatus::Bad(
format!(
"Optional pair with null optval should have 1 element. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs,
wasm = wasm
)
.into(),
)],
children: Vec::new(),
name: "".into(),
});
}
let emacs_val = emacs
.first()
.expect("If-statement proves this will be Some.");
2023-12-29 21:56:02 +00:00
result.extend(compare_json_value(source, emacs_val, wasm_val)?)?;
2023-12-29 21:51:52 +00:00
} else {
// If the optval is not null, then the elisp should have 3 values, the optval, a dot, and the val.
if emacs.len() != 3 {
return Ok(WasmDiffResult {
status: vec![WasmDiffStatus::Bad(
format!(
"Optional pair with non-null optval should have 3 elements. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs,
wasm = wasm
)
.into(),
)],
children: Vec::new(),
name: "".into(),
});
}
let emacs_optval = emacs
.get(2)
2023-12-29 21:51:52 +00:00
.expect("If-statement proves this will be Some.");
let emacs_val = emacs
.first()
2023-12-29 21:51:52 +00:00
.expect("If-statement proves this will be Some.");
2023-12-29 21:56:02 +00:00
result.extend(compare_json_value(source, emacs_optval, wasm_optval)?)?;
result.extend(compare_json_value(source, emacs_val, wasm_val)?)?;
2023-12-29 21:51:52 +00:00
}
Ok(result)
}
2023-12-29 22:46:35 +00:00
fn compare_object_tree<'e, 's, 'w>(
source: &'s str,
emacs: &'e Vec<Token<'s>>,
wasm: &'w serde_json::Map<String, serde_json::Value>,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
let mut result = WasmDiffResult::default();
let wasm_attributes = wasm
.get("object-tree")
.ok_or(r#"Wasm object tree should have an "object-tree" attribute."#)?
.as_array()
.ok_or(r#"Wasm "object-tree" attribute should be a list."#)?;
let emacs_outer_length = emacs.len();
let wasm_outer_length = wasm_attributes.len();
if emacs_outer_length != wasm_outer_length {
result.status.push(WasmDiffStatus::Bad(
format!(
"Child length mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_outer_length,
wasm = wasm_outer_length
)
.into(),
));
return Ok(result);
}
for (emacs_attribute, wasm_attribute) in emacs.iter().zip(wasm_attributes.iter()) {
let emacs_attribute = emacs_attribute.as_list()?;
let wasm_attribute = wasm_attribute
.as_array()
.ok_or("Wasm middle layer in object tree should be a list.")?;
if wasm_attribute.len() != 2 {
result.status.push(WasmDiffStatus::Bad(
format!(
"Wasm middle layer in object tree should have a length of 2. Wasm=({wasm:?}).",
wasm = wasm_attribute.len()
)
.into(),
));
return Ok(result);
}
if let Some(serde_json::Value::Null) = wasm_attribute.first() {
// If optval is null then the emacs array should only contain 1 value.
if emacs_attribute.len() != 1 {
result.status.push(WasmDiffStatus::Bad(
format!(
"Emacs middle layer in object tree should have a length of 1. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_attribute,
wasm = wasm_attribute
)
.into(),
));
return Ok(result);
}
let emacs_val = emacs_attribute
.first()
.expect("If-statement proves this will be Some.");
let wasm_val = wasm_attribute
.get(1)
.expect("If-statement proves this will be Some.");
result.extend(compare_json_value(source, emacs_val, wasm_val)?)?;
} else {
2023-12-30 03:58:32 +00:00
// If optval is not null, then the emacs array should contain a list, the first child of which is a list for optval, and all other entries to the outer list are the val.
if emacs_attribute.len() < 2 {
2023-12-29 22:46:35 +00:00
result.status.push(WasmDiffStatus::Bad(
format!(
2023-12-30 03:58:32 +00:00
"Emacs middle layer in object tree should have a length of at least 2. Emacs=({emacs:?}) Wasm=({wasm:?}).",
2023-12-29 22:46:35 +00:00
emacs = emacs_attribute,
wasm = wasm_attribute
)
.into(),
));
return Ok(result);
}
2023-12-30 03:58:32 +00:00
let emacs_optval = emacs_attribute.iter().skip(1);
2023-12-29 22:46:35 +00:00
let wasm_optval = wasm_attribute
.first()
2023-12-30 03:58:32 +00:00
.expect("If-statement proves this will be Some.")
.as_array()
.ok_or("first value in wasm object tree should be a list.")?;
2023-12-29 22:46:35 +00:00
let emacs_val = emacs_attribute
2023-12-30 03:58:32 +00:00
.first()
.ok_or("If-statement proves this will be Some.")?
.as_list()?;
2023-12-29 22:46:35 +00:00
let wasm_val = wasm_attribute
.get(1)
2023-12-30 03:58:32 +00:00
.expect("If-statement proves this will be Some.")
.as_array()
.ok_or("2nd value in wasm object tree should be a list.")?;
result.extend(wasm_compare_list(source, emacs_optval, wasm_optval.iter())?)?;
result.extend(wasm_compare_list(
source,
emacs_val.iter(),
wasm_val.iter(),
)?)?;
2023-12-29 22:46:35 +00:00
}
}
Ok(result)
}
2023-12-30 22:09:12 +00:00
fn compare_number_lines<'e, 's, 'w>(
source: &'s str,
emacs: &'e Vec<Token<'s>>,
wasm: &'w serde_json::Map<String, serde_json::Value>,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
let mut result = WasmDiffResult::default();
let mut emacs_iter = emacs.iter();
let emacs_directive = emacs_iter
.next()
.ok_or("Emacs number lines should have 3 children.")?
.as_atom()?;
let emacs_number: i64 = emacs_iter
.skip(1)
.next()
.ok_or("Emacs number lines should have 3 children.")?
.as_atom()?
.parse()?;
if wasm.contains_key("new") {
let wasm_number = wasm
.get("new")
.ok_or(r#"Wasm number lines should have a "new" attribute."#)?
.as_i64()
.ok_or(r#"Wasm number lines "new" attribute should be a number."#)?;
if emacs_directive != "new" {
result.status.push(WasmDiffStatus::Bad(
format!(
"Number lines directive mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_directive,
wasm = "new"
)
.into(),
));
return Ok(result);
}
if emacs_number != wasm_number {
result.status.push(WasmDiffStatus::Bad(
format!(
"Number lines number mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_number,
wasm = wasm_number
)
.into(),
));
return Ok(result);
}
} else if wasm.contains_key("continued") {
let wasm_number = wasm
.get("continued")
.ok_or(r#"Wasm number lines should have a "continued" attribute."#)?
.as_i64()
.ok_or(r#"Wasm number lines "continued" attribute should be a number."#)?;
if emacs_directive != "continued" {
result.status.push(WasmDiffStatus::Bad(
format!(
"Number lines directive mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_directive,
wasm = "continued"
)
.into(),
));
return Ok(result);
}
if emacs_number != wasm_number {
result.status.push(WasmDiffStatus::Bad(
format!(
"Number lines number mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_number,
wasm = wasm_number
)
.into(),
));
return Ok(result);
}
} else {
result.status.push(WasmDiffStatus::Bad(
format!(
"Unrecognized number lines directive. Wasm=({wasm:?}).",
wasm = wasm
)
.into(),
));
return Ok(result);
}
Ok(result)
}
2023-12-30 23:25:15 +00:00
fn compare_string_set<'e, 's, 'w>(
source: &'s str,
emacs: &'e Vec<Token<'s>>,
wasm: &'w serde_json::Map<String, serde_json::Value>,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
let mut result = WasmDiffResult::default();
let wasm_list = wasm
.get("value")
.ok_or(r#"Wasm string set should have a "value" attribute."#)?
.as_array()
.ok_or(r#"Wasm string set "value" attribute should be a list."#)?;
let wasm_strings = wasm_list
.iter()
.map(|v| v.as_str().ok_or("Non-string in wasm string set."))
.collect::<Result<BTreeSet<_>, &'static str>>()?;
let emacs_strings = emacs
.iter()
.map(|v| v.as_atom())
.collect::<Result<Vec<_>, Box<dyn std::error::Error>>>()?
.into_iter()
.map(|s| unquote(s))
.collect::<Result<Vec<_>, Box<dyn std::error::Error>>>()?;
let emacs_strings = emacs_strings
.iter()
.map(|s| s.borrow())
.collect::<BTreeSet<&str>>();
let mismatched: Vec<_> = emacs_strings
.symmetric_difference(&wasm_strings)
.copied()
.collect();
if !mismatched.is_empty() {
result.status.push(WasmDiffStatus::Bad(
format!(
"String set mismatch. MismatchedValues=({values:?}).",
values = mismatched.join(", ")
)
.into(),
));
}
Ok(result)
}
2023-12-29 22:24:38 +00:00
fn is_plain_text<'e, 's, 'w>(wasm: &'w serde_json::Map<String, serde_json::Value>) -> bool {
if let Some(serde_json::Value::String(node_type)) = wasm.get("ast-node") {
node_type == "plain-text"
} else {
false
}
}
fn compare_plain_text<'e, 's, 'w>(
source: &'s str,
emacs: &'e TextWithProperties<'s>,
wasm: &'w serde_json::Map<String, serde_json::Value>,
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
let mut result = WasmDiffResult::default();
2023-12-29 22:26:28 +00:00
result.name = "plain-text".into();
2023-12-29 22:24:38 +00:00
if !is_plain_text(wasm) {
result.status.push(WasmDiffStatus::Bad(
format!(
"AST node type mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs,
wasm = wasm,
)
.into(),
));
return Ok(result);
}
let emacs_text = unquote(emacs.text)?;
let wasm_standard_properties = wasm
.get("standard-properties")
.ok_or(r#"Wasm AST nodes should have a "standard-properties" attribute."#)?
.as_object()
.ok_or(r#"Wasm ast node "standard-properties" attribute should be an object."#)?;
let wasm_begin = {
if let Some(serde_json::Value::Number(begin)) = wasm_standard_properties.get("begin") {
begin
.as_u64()
.map(|w| w as usize)
.ok_or("Begin should be a number.")?
} else {
result.status.push(WasmDiffStatus::Bad(
format!(
"AST node type mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs,
wasm = wasm,
)
.into(),
));
return Ok(result);
}
};
let wasm_end = {
if let Some(serde_json::Value::Number(end)) = wasm_standard_properties.get("end") {
end.as_u64()
.map(|w| w as usize)
.ok_or("End should be a number.")?
} else {
result.status.push(WasmDiffStatus::Bad(
format!(
"AST node type mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs,
wasm = wasm,
)
.into(),
));
return Ok(result);
}
};
let wasm_text: String = source
.chars()
.skip(wasm_begin - 1)
.take(wasm_end - wasm_begin)
.collect();
if wasm_text != emacs_text {
result.status.push(WasmDiffStatus::Bad(
format!(
"Text mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_text,
wasm = wasm_text,
)
.into(),
));
return Ok(result);
}
let emacs_start = emacs
.properties
.first()
.map(|t| t.as_atom())
.map_or(Ok(None), |r| r.map(Some))?;
if emacs_start != Some("0") {
result.status.push(WasmDiffStatus::Bad(
format!(
"Text should start at offset 0. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs,
wasm = wasm,
)
.into(),
));
}
let emacs_end = emacs
.properties
.get(1)
.map(|t| t.as_atom())
.map_or(Ok(None), |r| r.map(Some))?;
if emacs_end != Some((wasm_end - wasm_begin).to_string().as_str()) {
result.status.push(WasmDiffStatus::Bad(
format!(
"Text end mismatch. Emacs=({emacs:?}) Wasm=({wasm:?}).",
emacs = emacs_end,
wasm = wasm_end - wasm_begin,
)
.into(),
));
}
Ok(result)
}