2023-12-30 18:25:15 -05:00
use std ::borrow ::Borrow ;
2023-12-29 11:37:30 -05:00
use std ::borrow ::Cow ;
2023-12-30 18:25:15 -05:00
use std ::collections ::BTreeSet ;
2023-12-29 15:38:18 -05:00
use std ::collections ::HashMap ;
2023-12-29 11:37:30 -05:00
2023-12-27 19:15:39 -05:00
use super ::diff ::WasmDiffResult ;
use super ::diff ::WasmDiffStatus ;
2023-12-31 11:11:25 -05:00
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 ;
2023-12-29 15:03:36 -05:00
use crate ::wasm ::WasmAstNodeWrapper ;
2023-12-27 09:31:54 -05:00
use crate ::wasm ::WasmDocument ;
2023-12-30 22:42:14 -05:00
pub fn wasm_compare_document < ' s > (
2023-12-27 11:10:40 -05:00
source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : & Token < ' s > ,
wasm : & WasmAstNodeWrapper < WasmDocument > ,
2023-12-29 11:37:30 -05:00
) -> 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 16:56:02 -05:00
compare_json_value ( source , emacs , & wasm_json_parsed )
2023-12-29 11:37:30 -05:00
}
2023-12-30 22:42:14 -05:00
fn compare_json_value < ' s > (
2023-12-29 11:37:30 -05:00
source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : & Token < ' s > ,
2023-12-29 16:56:02 -05:00
wasm : & serde_json ::Value ,
2023-12-29 11:37:30 -05:00
) -> Result < WasmDiffResult < ' s > , Box < dyn std ::error ::Error > > {
2023-12-29 17:46:35 -05:00
// println!("XXXXXXXXXXXXXX compare_json_value XXXXXXXXXXXXXX");
// println!("{:?}", emacs);
// println!("{:?}", wasm);
// println!("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
2023-12-29 16:56:02 -05:00
match ( wasm , emacs ) {
2023-12-29 15:41:41 -05:00
( serde_json ::Value ::Object ( wasm ) , Token ::List ( el ) ) if wasm . contains_key ( " ast-node " ) = > {
2023-12-29 11:37:30 -05:00
// We hit a regular ast node.
compare_ast_node ( source , el , wasm )
}
2023-12-29 15:09:54 -05:00
( 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 16:51:52 -05:00
( serde_json ::Value ::Array ( w ) , Token ::List ( e ) ) = > {
2023-12-29 17:46:35 -05:00
// TODO: This is creating children with no names.
2023-12-29 16:51:52 -05: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 17:46:35 -05: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 17:09:12 -05: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 18:25:15 -05: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 17:24:38 -05:00
( serde_json ::Value ::Object ( w ) , Token ::TextWithProperties ( e ) ) if is_plain_text ( w ) = > {
compare_plain_text ( source , e , w )
}
2023-12-29 19:21:35 -05:00
( serde_json ::Value ::Null , Token ::Atom ( " nil " ) ) = > Ok ( WasmDiffResult ::default ( ) ) ,
2023-12-29 20:12:45 -05: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 ( ) )
}
2023-12-29 23:21:30 -05:00
( 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-29 20:12:45 -05: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-30 20:50:28 -05:00
( serde_json ::Value ::Object ( w ) , _ ) if w . contains_key ( " noop " ) = > {
2023-12-29 23:21:30 -05:00
Ok ( WasmDiffResult ::default ( ) )
}
2023-12-29 11:37:30 -05:00
( 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-29 20:12:45 -05:00
// (serde_json::Value::Bool(_), Token::Atom(_)) => todo!(),
2023-12-29 11:37:30 -05:00
( serde_json ::Value ::Bool ( _ ) , Token ::List ( _ ) ) = > todo! ( ) ,
( serde_json ::Value ::Bool ( _ ) , Token ::TextWithProperties ( _ ) ) = > todo! ( ) ,
( serde_json ::Value ::Bool ( _ ) , Token ::Vector ( _ ) ) = > todo! ( ) ,
2023-12-29 23:21:30 -05:00
// (serde_json::Value::Number(_), Token::Atom(_)) => todo!(),
2023-12-29 11:37:30 -05:00
( serde_json ::Value ::Number ( _ ) , Token ::List ( _ ) ) = > todo! ( ) ,
( serde_json ::Value ::Number ( _ ) , Token ::TextWithProperties ( _ ) ) = > todo! ( ) ,
( serde_json ::Value ::Number ( _ ) , Token ::Vector ( _ ) ) = > todo! ( ) ,
2023-12-29 20:12:45 -05: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 )
}
2023-12-29 11:37:30 -05:00
( 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 16:51:52 -05:00
// (serde_json::Value::Array(_), Token::List(_)) => todo!(),
2023-12-29 11:37:30 -05:00
( 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 22:42:14 -05:00
fn compare_optional_json_value < ' s > (
2023-12-29 20:12:45 -05:00
source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : Option < & Token < ' s > > ,
2023-12-29 20:12:45 -05:00
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-30 20:47:17 -05:00
( None , Some ( serde_json ::Value ::Object ( w ) ) ) if w . contains_key ( " noop " ) = > {
Ok ( WasmDiffResult ::default ( ) )
}
2023-12-29 20:12:45 -05: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-30 22:42:14 -05:00
fn compare_ast_node < ' s > (
2023-12-29 11:37:30 -05:00
source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : & [ Token < ' s > ] ,
wasm : & serde_json ::Map < String , serde_json ::Value > ,
2023-12-29 11:37:30 -05:00
) -> 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
2023-12-29 15:41:41 -05:00
. get ( " ast-node " )
2023-12-29 11:37:30 -05:00
. 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 ( ) ? ;
2023-12-29 15:03:36 -05:00
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."# ) ? ;
2023-12-29 11:37:30 -05:00
{
// Compare attribute names.
let emacs_keys : std ::collections ::BTreeSet < String > = emacs_attributes_map
. keys ( )
. map ( | s | ( * s ) . to_owned ( ) )
. collect ( ) ;
2023-12-29 20:12:45 -05: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)
2023-12-29 15:03:36 -05:00
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 ( ) ;
2023-12-29 11:37:30 -05:00
let emacs_only_attributes : Vec < & String > = emacs_keys . difference ( & wasm_keys ) . collect ( ) ;
2023-12-29 20:12:45 -05: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 ( ) ;
2023-12-29 11:37:30 -05:00
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.
2023-12-29 15:03:36 -05:00
for attribute_name in wasm_attributes_map . keys ( ) {
2023-12-30 22:42:14 -05:00
let mut layer = WasmDiffResult ::< '_ > {
name : Cow ::Owned ( attribute_name . clone ( ) ) ,
.. Default ::default ( )
} ;
2023-12-29 20:12:45 -05:00
let wasm_attribute_value = wasm_attributes_map . get ( attribute_name ) ;
2023-12-29 11:37:30 -05:00
let emacs_key = wasm_key_to_emacs_key ( attribute_name ) ;
2023-12-30 22:42:14 -05:00
let emacs_attribute_value = emacs_attributes_map . get ( emacs_key . as_str ( ) ) . copied ( ) ;
2023-12-29 21:27:05 -05: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 ) ? ;
}
2023-12-29 11:37:30 -05:00
result . children . push ( layer ) ;
}
}
2023-12-29 15:38:18 -05:00
{
// Compare standard-properties.
2023-12-30 22:42:14 -05:00
let mut layer = WasmDiffResult ::< '_ > {
name : " standard-properties " . into ( ) ,
.. Default ::default ( )
} ;
2023-12-29 15:38:18 -05:00
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 " ) ,
2023-12-29 15:41:41 -05:00
( emacs_standard_properties . contents_begin , " contents-begin " ) ,
( emacs_standard_properties . contents_end , " contents-end " ) ,
( emacs_standard_properties . post_blank , " post-blank " ) ,
2023-12-29 15:38:18 -05:00
] {
match ( emacs_value , wasm_standard_properties . get ( wasm_name ) ) {
2023-12-29 19:21:35 -05:00
( None , None ) | ( None , Some ( serde_json ::Value ::Null ) ) = > { }
2023-12-29 15:38:18 -05: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 16:06:07 -05:00
{
// Compare children.
2023-12-30 22:42:14 -05:00
let mut layer = WasmDiffResult ::< '_ > {
name : " children " . into ( ) ,
.. Default ::default ( )
} ;
2023-12-29 16:06:07 -05:00
if let Some ( wasm_iter ) = wasm
. get ( " children " )
2023-12-30 22:42:14 -05:00
. and_then ( | children | children . as_array ( ) )
2023-12-29 16:06:07 -05:00
. 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 ) ;
}
2023-12-29 11:37:30 -05:00
Ok ( result )
}
fn wasm_key_to_emacs_key < WK : std ::fmt ::Display > ( wasm_key : WK ) -> String {
format! ( " : {key} " , key = wasm_key )
}
2023-12-30 22:42:14 -05:00
fn compare_quoted_string < ' s > (
2023-12-30 22:22:32 -05:00
_source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : & str ,
wasm : & String ,
2023-12-29 15:09:54 -05:00
) -> Result < WasmDiffResult < ' s > , Box < dyn std ::error ::Error > > {
let mut result = WasmDiffResult ::default ( ) ;
2023-12-29 17:26:28 -05: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 ( ) ,
) ) ;
}
2023-12-29 15:09:54 -05:00
Ok ( result )
}
2023-12-29 15:38:18 -05: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 16:06:07 -05:00
fn wasm_compare_list < ' e , ' s : ' e , ' w , EI , WI > (
source : & ' s str ,
2023-12-29 21:56:31 -05:00
mut emacs : EI ,
2023-12-29 16:06:07 -05: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 ( ) ;
2023-12-30 22:42:14 -05:00
if emacs_length = = 1 & & wasm_length = = 0 & & emacs . all ( | t | matches! ( t . as_atom ( ) , Ok ( r # """"# ) ) ) {
return Ok ( WasmDiffResult ::default ( ) ) ;
2023-12-29 21:56:31 -05:00
}
2023-12-29 16:06:07 -05: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 16:56:02 -05:00
child_status . push ( compare_json_value ( source , emacs_child , wasm_child ) ? ) ;
2023-12-29 16:06:07 -05:00
}
Ok ( WasmDiffResult {
status : Vec ::new ( ) ,
children : child_status ,
name : " " . into ( ) ,
} )
}
2023-12-30 22:42:14 -05:00
fn compare_optional_pair < ' s > (
2023-12-29 16:51:52 -05:00
source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : & Vec < Token < ' s > > ,
wasm : & serde_json ::Map < String , serde_json ::Value > ,
2023-12-29 16:51:52 -05:00
) -> 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 16:56:02 -05:00
result . extend ( compare_json_value ( source , emacs_val , wasm_val ) ? ) ? ;
2023-12-29 16:51:52 -05: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
2023-12-30 18:00:26 -05:00
. get ( 2 )
2023-12-29 16:51:52 -05:00
. expect ( " If-statement proves this will be Some. " ) ;
let emacs_val = emacs
2023-12-30 18:00:26 -05:00
. first ( )
2023-12-29 16:51:52 -05:00
. expect ( " If-statement proves this will be Some. " ) ;
2023-12-29 16:56:02 -05:00
result . extend ( compare_json_value ( source , emacs_optval , wasm_optval ) ? ) ? ;
result . extend ( compare_json_value ( source , emacs_val , wasm_val ) ? ) ? ;
2023-12-29 16:51:52 -05:00
}
Ok ( result )
}
2023-12-30 22:42:14 -05:00
fn compare_object_tree < ' s > (
2023-12-29 17:46:35 -05:00
source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : & [ Token < ' s > ] ,
wasm : & serde_json ::Map < String , serde_json ::Value > ,
2023-12-29 17:46:35 -05:00
) -> 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 ) ;
}
2023-12-30 21:26:23 -05:00
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 ( ) ? ;
2023-12-29 17:46:35 -05:00
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-29 22:58:32 -05: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 17:46:35 -05:00
result . status . push ( WasmDiffStatus ::Bad (
format! (
2023-12-29 22:58:32 -05:00
" Emacs middle layer in object tree should have a length of at least 2. Emacs=({emacs:?}) Wasm=({wasm:?}). " ,
2023-12-29 17:46:35 -05:00
emacs = emacs_attribute ,
wasm = wasm_attribute
)
. into ( ) ,
) ) ;
return Ok ( result ) ;
}
2023-12-29 22:58:32 -05:00
let emacs_optval = emacs_attribute . iter ( ) . skip ( 1 ) ;
2023-12-29 17:46:35 -05:00
let wasm_optval = wasm_attribute
. first ( )
2023-12-29 22:58:32 -05: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 17:46:35 -05:00
let emacs_val = emacs_attribute
2023-12-29 22:58:32 -05:00
. first ( )
2023-12-30 21:26:23 -05:00
. ok_or ( " If-statement proves this will be Some. " ) ? ;
2023-12-29 17:46:35 -05:00
let wasm_val = wasm_attribute
. get ( 1 )
2023-12-29 22:58:32 -05: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 ( ) ) ? ) ? ;
2023-12-30 21:26:23 -05:00
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 ( ) ,
) ? ) ? ;
}
2023-12-29 17:46:35 -05:00
}
}
Ok ( result )
}
2023-12-30 22:42:14 -05:00
fn compare_number_lines < ' s > (
2023-12-30 22:22:32 -05:00
_source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : & [ Token < ' s > ] ,
wasm : & serde_json ::Map < String , serde_json ::Value > ,
2023-12-30 17:09:12 -05:00
) -> 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
2023-12-30 22:42:14 -05:00
. nth ( 1 )
2023-12-30 17:09:12 -05:00
. 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 22:42:14 -05:00
fn compare_string_set < ' s > (
2023-12-30 22:22:32 -05:00
_source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : & [ Token < ' s > ] ,
wasm : & serde_json ::Map < String , serde_json ::Value > ,
2023-12-30 18:25:15 -05:00
) -> 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 ( )
2023-12-30 22:42:14 -05:00
. map ( unquote )
2023-12-30 18:25:15 -05:00
. 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-30 22:42:14 -05:00
fn is_plain_text ( wasm : & serde_json ::Map < String , serde_json ::Value > ) -> bool {
2023-12-29 17:24:38 -05:00
if let Some ( serde_json ::Value ::String ( node_type ) ) = wasm . get ( " ast-node " ) {
node_type = = " plain-text "
} else {
false
}
}
2023-12-30 22:42:14 -05:00
fn compare_plain_text < ' s > (
2023-12-29 17:24:38 -05:00
source : & ' s str ,
2023-12-30 22:42:14 -05:00
emacs : & TextWithProperties < ' s > ,
wasm : & serde_json ::Map < String , serde_json ::Value > ,
2023-12-29 17:24:38 -05:00
) -> Result < WasmDiffResult < ' s > , Box < dyn std ::error ::Error > > {
2023-12-30 22:42:14 -05:00
let mut result = WasmDiffResult ::< '_ > {
name : " plain-text " . into ( ) ,
.. Default ::default ( )
} ;
2023-12-29 17:24:38 -05: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 )
}