913 lines
33 KiB
Rust
913 lines
33 KiB
Rust
use std::borrow::Borrow;
|
|
use std::borrow::Cow;
|
|
use std::collections::BTreeSet;
|
|
use std::collections::HashMap;
|
|
|
|
use super::diff::WasmDiffResult;
|
|
use super::diff::WasmDiffStatus;
|
|
use crate::util::elisp::maybe_token_to_usize;
|
|
use crate::util::elisp::unquote;
|
|
use crate::util::elisp::EmacsStandardProperties;
|
|
use crate::util::elisp::TextWithProperties;
|
|
use crate::util::elisp::Token;
|
|
use crate::wasm::WasmAstNodeWrapper;
|
|
use crate::wasm::WasmDocument;
|
|
|
|
pub fn wasm_compare_document<'s>(
|
|
source: &'s str,
|
|
emacs: &Token<'s>,
|
|
wasm: &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)?;
|
|
compare_json_value(source, emacs, &wasm_json_parsed)
|
|
}
|
|
|
|
fn compare_json_value<'s>(
|
|
source: &'s str,
|
|
emacs: &Token<'s>,
|
|
wasm: &serde_json::Value,
|
|
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
|
|
// println!("XXXXXXXXXXXXXX compare_json_value XXXXXXXXXXXXXX");
|
|
// println!("{:?}", emacs);
|
|
// println!("{:?}", wasm);
|
|
// println!("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
|
|
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)
|
|
}
|
|
(serde_json::Value::Array(w), Token::List(e)) => {
|
|
// TODO: This is creating children with no names.
|
|
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)
|
|
}
|
|
(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)
|
|
}
|
|
(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)
|
|
}
|
|
(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)
|
|
}
|
|
(serde_json::Value::Object(w), Token::TextWithProperties(e)) if is_plain_text(w) => {
|
|
compare_plain_text(source, e, w)
|
|
}
|
|
(serde_json::Value::Null, Token::Atom("nil")) => Ok(WasmDiffResult::default()),
|
|
(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)
|
|
}
|
|
(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())
|
|
}
|
|
(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!(),
|
|
// (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(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!(),
|
|
// (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_optional_json_value<'s>(
|
|
source: &'s str,
|
|
emacs: Option<&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())
|
|
}
|
|
(None, Some(serde_json::Value::Object(w))) if w.contains_key("noop") => {
|
|
Ok(WasmDiffResult::default())
|
|
}
|
|
(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(),
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn compare_ast_node<'s>(
|
|
source: &'s str,
|
|
emacs: &[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()?;
|
|
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();
|
|
// 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();
|
|
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::<'_> {
|
|
name: Cow::Owned(attribute_name.clone()),
|
|
..Default::default()
|
|
};
|
|
let wasm_attribute_value = wasm_attributes_map.get(attribute_name);
|
|
let emacs_key = wasm_key_to_emacs_key(attribute_name);
|
|
let emacs_attribute_value = emacs_attributes_map.get(emacs_key.as_str()).copied();
|
|
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);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Compare standard-properties.
|
|
let mut layer = WasmDiffResult::<'_> {
|
|
name: "standard-properties".into(),
|
|
..Default::default()
|
|
};
|
|
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"),
|
|
] {
|
|
match (emacs_value, wasm_standard_properties.get(wasm_name)) {
|
|
(None, None) | (None, Some(serde_json::Value::Null)) => {}
|
|
(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);
|
|
}
|
|
|
|
{
|
|
// Compare children.
|
|
let mut layer = WasmDiffResult::<'_> {
|
|
name: "children".into(),
|
|
..Default::default()
|
|
};
|
|
if let Some(wasm_iter) = wasm
|
|
.get("children")
|
|
.and_then(|children| children.as_array())
|
|
.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<'s>(
|
|
_source: &'s str,
|
|
emacs: &str,
|
|
wasm: &String,
|
|
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
|
|
let mut result = WasmDiffResult::default();
|
|
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)
|
|
}
|
|
|
|
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,
|
|
}
|
|
})
|
|
}
|
|
|
|
fn wasm_compare_list<'e, 's: 'e, 'w, EI, WI>(
|
|
source: &'s str,
|
|
mut emacs: EI,
|
|
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 && emacs.all(|t| matches!(t.as_atom(), Ok(r#""""#))) {
|
|
return Ok(WasmDiffResult::default());
|
|
}
|
|
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) {
|
|
child_status.push(compare_json_value(source, emacs_child, wasm_child)?);
|
|
}
|
|
Ok(WasmDiffResult {
|
|
status: Vec::new(),
|
|
children: child_status,
|
|
name: "".into(),
|
|
})
|
|
}
|
|
|
|
fn compare_optional_pair<'s>(
|
|
source: &'s str,
|
|
emacs: &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 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.");
|
|
result.extend(compare_json_value(source, emacs_val, wasm_val)?)?;
|
|
} 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)
|
|
.expect("If-statement proves this will be Some.");
|
|
let emacs_val = emacs
|
|
.first()
|
|
.expect("If-statement proves this will be Some.");
|
|
result.extend(compare_json_value(source, emacs_optval, wasm_optval)?)?;
|
|
result.extend(compare_json_value(source, emacs_val, wasm_val)?)?;
|
|
}
|
|
Ok(result)
|
|
}
|
|
|
|
fn compare_object_tree<'s>(
|
|
source: &'s str,
|
|
emacs: &[Token<'s>],
|
|
wasm: &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 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 Ok("nil") = emacs_attribute.as_atom() {
|
|
if let Some(serde_json::Value::Null) = wasm_attribute.first() {
|
|
if let Some(serde_json::Value::Array(w)) = wasm_attribute.get(1) {
|
|
if w.is_empty() {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let emacs_attribute = emacs_attribute.as_list()?;
|
|
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 {
|
|
// 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 {
|
|
result.status.push(WasmDiffStatus::Bad(
|
|
format!(
|
|
"Emacs middle layer in object tree should have a length of at least 2. Emacs=({emacs:?}) Wasm=({wasm:?}).",
|
|
emacs = emacs_attribute,
|
|
wasm = wasm_attribute
|
|
)
|
|
.into(),
|
|
));
|
|
return Ok(result);
|
|
}
|
|
let emacs_optval = emacs_attribute.iter().skip(1);
|
|
let wasm_optval = wasm_attribute
|
|
.first()
|
|
.expect("If-statement proves this will be Some.")
|
|
.as_array()
|
|
.ok_or("first value in wasm object tree should be a list.")?;
|
|
let emacs_val = emacs_attribute
|
|
.first()
|
|
.ok_or("If-statement proves this will be Some.")?;
|
|
let wasm_val = wasm_attribute
|
|
.get(1)
|
|
.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())?)?;
|
|
if let Ok("nil") = emacs_val.as_atom() {
|
|
result.extend(wasm_compare_list(
|
|
source,
|
|
std::iter::empty(),
|
|
wasm_val.iter(),
|
|
)?)?;
|
|
} else {
|
|
result.extend(wasm_compare_list(
|
|
source,
|
|
emacs_val.as_list()?.iter(),
|
|
wasm_val.iter(),
|
|
)?)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
fn compare_number_lines<'s>(
|
|
_source: &'s str,
|
|
emacs: &[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_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
|
|
.nth(1)
|
|
.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)
|
|
}
|
|
|
|
fn compare_string_set<'s>(
|
|
_source: &'s str,
|
|
emacs: &[Token<'s>],
|
|
wasm: &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(unquote)
|
|
.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)
|
|
}
|
|
|
|
fn is_plain_text(wasm: &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<'s>(
|
|
source: &'s str,
|
|
emacs: &TextWithProperties<'s>,
|
|
wasm: &serde_json::Map<String, serde_json::Value>,
|
|
) -> Result<WasmDiffResult<'s>, Box<dyn std::error::Error>> {
|
|
let mut result = WasmDiffResult::<'_> {
|
|
name: "plain-text".into(),
|
|
..Default::default()
|
|
};
|
|
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)
|
|
}
|