#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/docs/core/address.md"))]
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct Address {
repr: Arc<str>,
hash: u64,
}
#[inline]
fn compute_address_hash(s: &str) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
s.hash(&mut hasher);
hasher.finish()
}
impl Address {
#[inline]
pub fn new(name: impl Into<Arc<str>>) -> Self {
let repr: Arc<str> = name.into();
let hash = compute_address_hash(&repr);
Address { repr, hash }
}
#[inline]
pub fn as_str(&self) -> &str {
&self.repr
}
}
impl Display for Address {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.repr)
}
}
impl Deref for Address {
type Target = str;
#[inline]
fn deref(&self) -> &str {
&self.repr
}
}
impl Hash for Address {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}
impl PartialEq for Address {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash && self.repr == other.repr
}
}
impl Eq for Address {}
impl PartialOrd for Address {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Address {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl From<String> for Address {
#[inline]
fn from(s: String) -> Self {
Address::new(s)
}
}
impl From<&str> for Address {
#[inline]
fn from(s: &str) -> Self {
Address::new(s)
}
}
pub const ADDR_INDEX_SEP: char = '#';
#[doc(hidden)]
pub fn escape_addr_segment(segment: &str) -> String {
if segment.contains('\\') || segment.contains('#') {
let mut out = String::with_capacity(segment.len() + 4);
for ch in segment.chars() {
match ch {
'\\' => out.push_str("\\\\"),
'#' => out.push_str("\\#"),
other => out.push(other),
}
}
out
} else {
segment.to_string()
}
}
#[doc(hidden)]
pub fn make_name(name: impl Display) -> String {
escape_addr_segment(&name.to_string())
}
#[doc(hidden)]
pub fn make_indexed(name: impl Display, index: impl Display) -> String {
format!(
"{}{}{}",
escape_addr_segment(&name.to_string()),
ADDR_INDEX_SEP,
escape_addr_segment(&index.to_string())
)
}
#[macro_export]
macro_rules! addr {
($name:expr) => {
$crate::core::address::Address::new($crate::core::address::make_name($name))
};
($name:expr, $i:expr) => {
$crate::core::address::Address::new($crate::core::address::make_indexed($name, $i))
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::{BTreeSet, HashSet};
#[test]
fn display_formats_inner_string() {
let a = Address::new("alpha");
assert_eq!(a.to_string(), "alpha");
}
#[test]
fn cached_hash_is_consistent_with_eq_and_clone() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
fn h(a: &Address) -> u64 {
let mut hasher = DefaultHasher::new();
a.hash(&mut hasher);
hasher.finish()
}
let a = addr!("mu", 7);
let b = addr!("mu", 7);
assert_eq!(a, b);
assert_eq!(h(&a), h(&b), "equal addresses must hash equally");
let c = a.clone();
assert_eq!(a, c);
assert!(Arc::ptr_eq(&a.repr, &c.repr));
assert_eq!(h(&a), h(&c));
let d = addr!("mu", 8);
assert_ne!(a, d);
}
#[test]
fn addr_macro_basic_and_indexed() {
let a = addr!("x");
assert_eq!(a.as_str(), "x");
let b = addr!("x", 3);
assert_eq!(b.as_str(), "x#3");
}
#[test]
fn addr_indexed_and_literal_hash_do_not_alias() {
let indexed = addr!("x", 3);
let literal_hash = addr!("x#3");
assert_ne!(indexed, literal_hash);
assert_eq!(indexed.as_str(), "x#3");
assert_eq!(literal_hash.as_str(), "x\\#3");
let a = addr!("a", "b#3");
let b = addr!("a#b", 3);
assert_ne!(a, b);
assert_eq!(a.as_str(), "a#b\\#3");
assert_eq!(b.as_str(), "a\\#b#3");
assert_ne!(addr!("a\\", 1), addr!("a\\#1"));
}
#[test]
fn addr_encoding_is_injective_across_hash_placements() {
let left = addr!("a#", "b");
let right = addr!("a", "#b");
assert_ne!(left, right);
assert_eq!(left.as_str(), "a\\##b");
assert_eq!(right.as_str(), "a#\\#b");
}
#[test]
fn equality_hash_and_ordering() {
let a1 = Address::new("x");
let a2 = Address::new("x");
let b = Address::new("y");
let mut set = HashSet::new();
set.insert(a1.clone());
set.insert(a2.clone());
set.insert(b.clone());
assert_eq!(set.len(), 2);
let mut bset = BTreeSet::new();
bset.insert(b);
bset.insert(a1);
let ordered: Vec<String> = bset.into_iter().map(|a| a.as_str().to_string()).collect();
assert_eq!(ordered, vec!["x".to_string(), "y".to_string()]);
}
}