organic/src/compare/util.rs

160 lines
5.5 KiB
Rust
Raw Normal View History

use crate::parser::sexp::Token;
use crate::parser::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.
pub 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 fn assert_name<'s>(emacs: &'s Token<'s>, name: &str) -> Result<(), Box<dyn std::error::Error>> {
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(())
}
2023-04-19 15:29:46 -04:00
pub fn assert_bounds<'s, S: Source<'s>>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s S,
) -> Result<(), Box<dyn std::error::Error>> {
2023-08-25 02:55:01 -04:00
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.")?,
2023-08-25 02:55:01 -04:00
);
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1) != begin || (rust_end + 1) != end {
2023-08-29 21:49:16 -04:00
Err(format!("Rust bounds (in bytes) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
2023-08-25 02:55:01 -04:00
}
Ok(())
}
struct StandardProperties {
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>,
}
fn get_standard_properties<'s>(
emacs: &'s Token<'s>,
) -> Result<StandardProperties, 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()?;
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
.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())?;
StandardProperties {
begin,
post_affiliated,
contents_begin,
contents_end,
end,
post_blank,
}
} 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))?;
StandardProperties {
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
pub fn get_property<'s, 'x>(
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))
}