use crate::parser::sexp::Token; use crate::types::Source; /// 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 } /// Get the offset into source that the rust object exists at. /// /// These offsets are zero-based unlike the elisp ones. fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize, usize) { let rust_object_source = rust_object.get_source(); assert!(is_slice_of(source, rust_object_source)); let offset = rust_object_source.as_ptr() as usize - source.as_ptr() as usize; let end = offset + rust_object_source.len(); (offset, end) } pub(crate) fn assert_name<'s>( emacs: &'s Token<'s>, name: &str, ) -> Result<(), Box> { 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!( "Expected a {expected} cell, but found a {found} cell.", expected = name, found = first_child ))?; } Ok(()) } pub(crate) fn assert_bounds<'s, S: Source<'s>>( source: &'s str, emacs: &'s Token<'s>, rust: &'s S, ) -> Result<(), Box> { let standard_properties = get_standard_properties(emacs)?; let (begin, end) = ( standard_properties .begin .ok_or("Token should have a begin.")?, standard_properties.end.ok_or("Token should have an end.")?, ); let (rust_begin, rust_end) = get_offsets(source, rust); let rust_begin_char_offset = (&source[..rust_begin]).chars().count(); let rust_end_char_offset = rust_begin_char_offset + (&source[rust_begin..rust_end]).chars().count(); if (rust_begin_char_offset + 1) != begin || (rust_end_char_offset + 1) != 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 + 1, rust_end = rust_end_char_offset + 1, emacs_begin=begin, emacs_end=end))?; } Ok(()) } struct StandardProperties { begin: Option, #[allow(dead_code)] post_affiliated: Option, #[allow(dead_code)] contents_begin: Option, #[allow(dead_code)] contents_end: Option, end: Option, #[allow(dead_code)] post_blank: Option, } fn get_standard_properties<'s>( emacs: &'s Token<'s>, ) -> Result> { 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 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()? .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())?; StandardProperties { begin, post_affiliated, contents_begin, contents_end, end, post_blank, } } else { 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))?; StandardProperties { begin, post_affiliated, contents_begin, contents_end, end, post_blank, } }) } fn maybe_token_to_usize( token: Option<&Token<'_>>, ) -> Result, Box> { 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::()) } }) .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))?) } /// 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. pub(crate) fn get_property<'s, 'x>( emacs: &'s Token<'s>, key: &'x str, ) -> Result>, Box> { 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)) }