2023-09-23 21:03:12 -04:00
use super ::elisp_fact ::GetElispFact ;
2023-09-11 15:31:48 -04:00
use super ::sexp ::Token ;
2023-09-29 17:28:50 -04:00
use crate ::compare ::sexp ::unquote ;
2023-09-23 21:03:12 -04:00
use crate ::types ::GetStandardProperties ;
2023-09-23 17:59:13 -04:00
use crate ::types ::StandardProperties ;
2023-04-11 19:16:04 -04:00
/// Check if the child string slice is a slice of the parent string slice.
fn is_slice_of ( parent : & str , child : & str ) -> bool {
let parent_start = parent . as_ptr ( ) as usize ;
let parent_end = parent_start + parent . len ( ) ;
let child_start = child . as_ptr ( ) as usize ;
let child_end = child_start + child . len ( ) ;
child_start > = parent_start & & child_end < = parent_end
}
2023-09-23 17:59:13 -04:00
/// Get the byte offset into source that the rust object exists at.
2023-04-11 19:16:04 -04:00
///
/// These offsets are zero-based unlike the elisp ones.
2023-09-23 19:35:12 -04:00
fn get_rust_byte_offsets < ' s , S : StandardProperties < ' s > + ? Sized > (
2023-09-23 17:59:13 -04:00
original_document : & ' s str ,
rust_ast_node : & ' s S ,
) -> ( usize , usize ) {
let rust_object_source = rust_ast_node . get_source ( ) ;
debug_assert! ( is_slice_of ( original_document , rust_object_source ) ) ;
let offset = rust_object_source . as_ptr ( ) as usize - original_document . as_ptr ( ) as usize ;
2023-04-11 19:16:04 -04:00
let end = offset + rust_object_source . len ( ) ;
( offset , end )
}
2023-04-19 15:19:21 -04:00
2023-09-23 21:03:12 -04:00
pub ( crate ) fn compare_standard_properties <
' s ,
S : GetStandardProperties < ' s > + GetElispFact < ' s > + ? Sized ,
> (
original_document : & ' s str ,
emacs : & ' s Token < ' s > ,
rust : & ' s S ,
) -> Result < ( ) , Box < dyn std ::error ::Error > > {
assert_name ( emacs , rust . get_elisp_fact ( ) . get_elisp_name ( ) ) ? ;
assert_bounds ( original_document , emacs , rust . get_standard_properties ( ) ) ? ;
Ok ( ( ) )
}
pub ( crate ) fn assert_name < ' s , S : AsRef < str > > (
2023-09-11 13:13:28 -04:00
emacs : & ' s Token < ' s > ,
2023-09-23 21:03:12 -04:00
name : S ,
2023-09-11 13:13:28 -04:00
) -> Result < ( ) , Box < dyn std ::error ::Error > > {
2023-09-23 21:03:12 -04:00
let name = name . as_ref ( ) ;
2023-04-19 15:19:21 -04:00
let children = emacs . as_list ( ) ? ;
let first_child = children
. first ( )
. ok_or ( " Should have at least one child. " ) ?
. as_atom ( ) ? ;
if first_child ! = name {
Err ( format! (
2023-09-23 21:03:12 -04:00
" AST node name mismatch. Expected a (rust) {expected} cell, but found a (emacs) {found} cell. " ,
2023-04-19 15:19:21 -04:00
expected = name ,
found = first_child
) ) ? ;
}
Ok ( ( ) )
}
2023-04-19 15:29:46 -04:00
2023-09-23 17:59:13 -04:00
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
///
/// This does **not** handle plain text because plain text is a special case.
2023-09-23 19:35:12 -04:00
pub ( crate ) fn assert_bounds < ' s , S : StandardProperties < ' s > + ? Sized > (
2023-09-23 17:59:13 -04:00
original_document : & ' s str ,
2023-04-19 15:29:46 -04:00
emacs : & ' s Token < ' s > ,
rust : & ' s S ,
) -> Result < ( ) , Box < dyn std ::error ::Error > > {
2023-09-23 17:59:13 -04:00
let standard_properties = get_emacs_standard_properties ( emacs ) ? ; // 1-based
2023-08-25 02:55:01 -04:00
let ( begin , end ) = (
standard_properties
. begin
. ok_or ( " Token should have a begin. " ) ? ,
2023-08-25 05:15:49 -04:00
standard_properties . end . ok_or ( " Token should have an end. " ) ? ,
2023-08-25 02:55:01 -04:00
) ;
2023-09-23 17:59:13 -04:00
let ( rust_begin , rust_end ) = get_rust_byte_offsets ( original_document , rust ) ; // 0-based
let rust_begin_char_offset = ( & original_document [ .. rust_begin ] ) . chars ( ) . count ( ) + 1 ; // 1-based
2023-08-31 20:19:28 -04:00
let rust_end_char_offset =
2023-09-23 17:59:13 -04:00
rust_begin_char_offset + ( & original_document [ rust_begin .. rust_end ] ) . chars ( ) . count ( ) ; // 1-based
if rust_begin_char_offset ! = begin | | rust_end_char_offset ! = end {
Err ( format! ( " Rust bounds (in chars) ( {rust_begin} , {rust_end} ) do not match emacs bounds ( {emacs_begin} , {emacs_end} ) " , rust_begin = rust_begin_char_offset , rust_end = rust_end_char_offset , emacs_begin = begin , emacs_end = end ) ) ? ;
2023-08-25 02:55:01 -04:00
}
Ok ( ( ) )
}
2023-09-23 17:44:54 -04:00
struct EmacsStandardProperties {
2023-08-25 02:55:01 -04:00
begin : Option < usize > ,
#[ allow(dead_code) ]
post_affiliated : Option < usize > ,
#[ allow(dead_code) ]
contents_begin : Option < usize > ,
#[ allow(dead_code) ]
contents_end : Option < usize > ,
end : Option < usize > ,
#[ allow(dead_code) ]
post_blank : Option < usize > ,
}
2023-09-23 17:44:54 -04:00
fn get_emacs_standard_properties < ' s > (
2023-08-25 02:55:01 -04:00
emacs : & ' s Token < ' s > ,
2023-09-23 17:44:54 -04:00
) -> Result < EmacsStandardProperties , Box < dyn std ::error ::Error > > {
2023-04-19 15:29:46 -04:00
let children = emacs . as_list ( ) ? ;
let attributes_child = children
. iter ( )
. nth ( 1 )
. ok_or ( " Should have an attributes child. " ) ? ;
let attributes_map = attributes_child . as_map ( ) ? ;
2023-08-13 01:06:55 -04:00
let standard_properties = attributes_map . get ( " :standard-properties " ) ;
2023-08-25 02:55:01 -04:00
Ok ( if standard_properties . is_some ( ) {
let mut std_props = standard_properties
2023-08-13 01:06:55 -04:00
. expect ( " if statement proves its Some " )
2023-08-25 02:55:01 -04:00
. as_vector ( ) ?
. into_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 ( ) ) ? ;
2023-09-23 17:44:54 -04:00
EmacsStandardProperties {
2023-08-25 02:55:01 -04:00
begin ,
post_affiliated ,
contents_begin ,
contents_end ,
end ,
post_blank ,
}
2023-08-13 01:06:55 -04:00
} else {
2023-08-25 02:55:01 -04:00
let begin = maybe_token_to_usize ( attributes_map . get ( " :begin " ) . map ( | token | * token ) ) ? ;
let end = maybe_token_to_usize ( attributes_map . get ( " :end " ) . map ( | token | * token ) ) ? ;
let contents_begin =
maybe_token_to_usize ( attributes_map . get ( " :contents-begin " ) . map ( | token | * token ) ) ? ;
let contents_end =
maybe_token_to_usize ( attributes_map . get ( " :contents-end " ) . map ( | token | * token ) ) ? ;
let post_blank =
maybe_token_to_usize ( attributes_map . get ( " :post-blank " ) . map ( | token | * token ) ) ? ;
let post_affiliated =
maybe_token_to_usize ( attributes_map . get ( " :post-affiliated " ) . map ( | token | * token ) ) ? ;
2023-09-23 17:44:54 -04:00
EmacsStandardProperties {
2023-08-25 02:55:01 -04:00
begin ,
post_affiliated ,
contents_begin ,
contents_end ,
end ,
post_blank ,
}
} )
}
2023-04-19 15:29:46 -04:00
2023-08-25 02:55:01 -04:00
fn maybe_token_to_usize (
token : Option < & Token < '_ > > ,
) -> Result < Option < usize > , Box < dyn std ::error ::Error > > {
Ok ( token
. map ( | token | token . as_atom ( ) )
. map_or ( Ok ( None ) , | r | r . map ( Some ) ) ?
. map ( | val | {
if val = = " nil " {
None
} else {
Some ( val . parse ::< usize > ( ) )
}
} )
. flatten ( ) // Outer option is whether or not the param exists, inner option is whether or not it is nil
. map_or ( Ok ( None ) , | r | r . map ( Some ) ) ? )
2023-04-19 15:29:46 -04:00
}
2023-08-29 22:07:23 -04:00
2023-09-08 15:57:24 -04:00
/// Get a named property from the emacs token.
///
/// Returns Ok(None) if value is nil.
///
/// Returns error if the attribute is not specified on the token at all.
2023-09-11 13:13:28 -04:00
pub ( crate ) fn get_property < ' s , ' x > (
2023-08-29 22:07:23 -04:00
emacs : & ' s Token < ' s > ,
key : & ' x str ,
) -> Result < Option < & ' s Token < ' s > > , Box < dyn std ::error ::Error > > {
let children = emacs . as_list ( ) ? ;
let attributes_child = children
. iter ( )
. nth ( 1 )
. ok_or ( " Should have an attributes child. " ) ? ;
let attributes_map = attributes_child . as_map ( ) ? ;
let prop = attributes_map
. get ( key )
. ok_or ( format! ( " Missing {} attribute. " , key ) ) ? ;
match prop . as_atom ( ) {
Ok ( " nil " ) = > return Ok ( None ) ,
_ = > { }
} ;
Ok ( Some ( * prop ) )
}
2023-09-29 13:03:01 -04:00
/// Get a named property containing an unquoted atom from the emacs token.
///
/// Returns None if key is not found.
pub ( crate ) fn get_property_unquoted_atom < ' s , ' x > (
emacs : & ' s Token < ' s > ,
key : & ' x str ,
) -> Result < Option < & ' s str > , Box < dyn std ::error ::Error > > {
Ok ( get_property ( emacs , key ) ?
. map ( Token ::as_atom )
. map_or ( Ok ( None ) , | r | r . map ( Some ) ) ? )
}
2023-09-29 17:28:50 -04:00
/// Get a named property containing an quoted string from the emacs token.
///
/// Returns None if key is not found.
pub ( crate ) fn get_property_quoted_string < ' s , ' x > (
emacs : & ' s Token < ' s > ,
key : & ' x str ,
) -> Result < Option < String > , Box < dyn std ::error ::Error > > {
Ok ( get_property ( emacs , key ) ?
. map ( Token ::as_atom )
. map_or ( Ok ( None ) , | r | r . map ( Some ) ) ?
. map ( unquote )
. map_or ( Ok ( None ) , | r | r . map ( Some ) ) ? )
}