use std::cmp::Ordering;
use std::fmt;
use std::hash::{BuildHasher, Hash, Hasher};
use std::sync::{Arc, OnceLock};
const P: u64 = (1 << 61) - 1;
fn base() -> u64 {
static BASE: OnceLock<u64> = OnceLock::new();
*BASE.get_or_init(|| {
let r = std::collections::hash_map::RandomState::new().hash_one(0x0000_e7a9_u64);
r.wrapping_rem(P - (1 << 20)).wrapping_add(1 << 20)
})
}
fn mulmod(a: u64, b: u64) -> u64 {
let x = u128::from(a).wrapping_mul(u128::from(b));
let lo = (x & u128::from(P)) as u64;
let hi = (x >> 61) as u64;
let s = lo.wrapping_add(hi);
let s = (s & P).wrapping_add(s >> 61);
if s >= P { s.wrapping_sub(P) } else { s }
}
fn addmod(a: u64, b: u64) -> u64 {
let s = a.wrapping_add(b);
if s >= P { s.wrapping_sub(P) } else { s }
}
fn digest(text: &str) -> (u64, u64) {
let b = base();
let (mut h, mut pow) = (0u64, 1u64);
for byte in text.bytes() {
h = addmod(mulmod(h, b), u64::from(byte).wrapping_add(1));
pow = mulmod(pow, b);
}
(h, pow)
}
#[derive(Debug)]
pub(crate) struct TagPrefix {
text: Box<str>,
digest: u64,
}
impl TagPrefix {
pub(crate) fn new(text: Box<str>) -> Self {
let (digest, _) = digest(&text);
Self { text, digest }
}
pub(crate) fn text(&self) -> &str {
&self.text
}
pub(crate) fn digest(&self) -> u64 {
self.digest
}
}
#[derive(Clone)]
pub struct Tag {
prefix: Option<Arc<TagPrefix>>,
suffix: Arc<str>,
digest: u64,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct TagStr<'a> {
a: &'a str,
b: &'a str,
}
impl<'a> From<&'a str> for TagStr<'a> {
fn from(s: &'a str) -> Self {
Self { a: s, b: "" }
}
}
impl<'a> TagStr<'a> {
pub(crate) fn len(self) -> usize {
self.a.len().saturating_add(self.b.len())
}
pub(crate) fn as_single(self) -> Option<&'a str> {
if self.b.is_empty() {
Some(self.a)
} else if self.a.is_empty() {
Some(self.b)
} else {
None
}
}
pub(crate) fn starts_with(self, p: &str) -> bool {
self.strip_prefix(p).is_some()
}
pub(crate) fn strip_prefix(self, p: &str) -> Option<Self> {
if let Some(rest) = self.a.strip_prefix(p) {
return Some(Self { a: rest, b: self.b });
}
let (head, tail) = (p.get(..self.a.len())?, p.get(self.a.len()..)?);
if head != self.a {
return None;
}
let rest = self.b.strip_prefix(tail)?;
Some(Self { a: rest, b: "" })
}
pub(crate) fn ends_with(self, p: &str) -> bool {
match p.len().checked_sub(self.b.len()) {
None => self.b.ends_with(p),
Some(cut) => {
p.get(cut..).is_some_and(|tail| tail == self.b)
&& p.get(..cut).is_some_and(|head| self.a.ends_with(head))
}
}
}
pub(crate) fn bytes(self) -> impl Iterator<Item = u8> + 'a {
self.a.bytes().chain(self.b.bytes())
}
pub(crate) fn chars(self) -> impl Iterator<Item = char> + 'a {
self.a.chars().chain(self.b.chars())
}
pub(crate) fn is(self, s: &str) -> bool {
self.len() == s.len() && self.strip_prefix(s).is_some()
}
pub(crate) fn to_owned_string(self) -> String {
let mut out = String::with_capacity(self.len());
out.push_str(self.a);
out.push_str(self.b);
out
}
}
impl PartialEq for TagStr<'_> {
fn eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.bytes().eq(other.bytes())
}
}
impl Eq for TagStr<'_> {}
impl PartialEq<&str> for TagStr<'_> {
fn eq(&self, other: &&str) -> bool {
self.is(other)
}
}
impl fmt::Display for TagStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.a)?;
f.write_str(self.b)
}
}
impl Tag {
#[must_use]
pub fn new(tag: impl Into<String>) -> Self {
Self::from_parts(None, Arc::from(tag.into()))
}
pub(crate) fn from_parts(prefix: Option<Arc<TagPrefix>>, suffix: Arc<str>) -> Self {
let (hs, pow) = digest(&suffix);
let digest = match &prefix {
Some(p) => addmod(mulmod(p.digest, pow), hs),
None => hs,
};
Self {
prefix,
suffix,
digest,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.parts().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn starts_with(&self, prefix: &str) -> bool {
self.parts().starts_with(prefix)
}
#[must_use]
pub fn ends_with(&self, suffix: &str) -> bool {
self.parts().ends_with(suffix)
}
#[cfg(feature = "__internal-checks")]
#[doc(hidden)]
#[must_use]
pub fn same_allocation(&self, other: &Tag) -> bool {
self.same(other)
}
#[cfg(feature = "__internal-checks")]
#[doc(hidden)]
#[must_use]
pub fn shares_prefix_with(&self, other: &Tag) -> bool {
self.prefix.is_some() && self.same_prefix(other)
}
pub(crate) fn parts(&self) -> TagStr<'_> {
TagStr {
a: self.prefix.as_ref().map_or("", |p| p.text()),
b: &self.suffix,
}
}
pub(crate) fn suffix(&self) -> &str {
&self.suffix
}
pub(crate) fn shared_prefix(&self) -> Option<&Arc<TagPrefix>> {
self.prefix.as_ref()
}
pub(crate) fn digest(&self) -> u64 {
self.digest
}
pub(crate) fn identity(&self) -> (usize, usize) {
(
Arc::as_ptr(&self.suffix).cast::<u8>().addr(),
self.prefix.as_ref().map_or(0, |p| Arc::as_ptr(p).addr()),
)
}
pub(crate) fn same(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.suffix, &other.suffix) && self.same_prefix(other)
}
pub(crate) fn same_prefix(&self, other: &Self) -> bool {
match (&self.prefix, &other.prefix) {
(None, None) => true,
(Some(a), Some(b)) => Arc::ptr_eq(a, b),
_ => false,
}
}
}
impl PartialEq for Tag {
fn eq(&self, other: &Self) -> bool {
if self.same(other) {
return true;
}
if self.digest != other.digest || self.len() != other.len() {
return false;
}
if self.same_prefix(other) {
return self.suffix == other.suffix;
}
self.parts() == other.parts()
}
}
impl Eq for Tag {}
impl Hash for Tag {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.digest);
}
}
impl PartialOrd for Tag {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Tag {
fn cmp(&self, other: &Self) -> Ordering {
if self.same(other) {
return Ordering::Equal;
}
if self.same_prefix(other) {
return self.suffix.cmp(&other.suffix);
}
self.parts().bytes().cmp(other.parts().bytes())
}
}
impl PartialEq<str> for Tag {
fn eq(&self, other: &str) -> bool {
self.parts().is(other)
}
}
impl PartialEq<&str> for Tag {
fn eq(&self, other: &&str) -> bool {
self.parts().is(other)
}
}
impl PartialEq<String> for Tag {
fn eq(&self, other: &String) -> bool {
self.parts().is(other)
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.parts(), f)
}
}
impl fmt::Debug for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Tag({self})")
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::indexing_slicing)]
use super::*;
use std::collections::hash_map::RandomState;
fn split(p: &str, s: &str) -> Tag {
Tag::from_parts(Some(Arc::new(TagPrefix::new(p.into()))), Arc::from(s))
}
#[test]
fn identity_is_the_logical_text() {
let s = RandomState::new();
let whole = Tag::new("tag:x,1:abc");
for t in [
split("tag:x,1:", "abc"),
split("tag:x,1:ab", "c"),
split("", "tag:x,1:abc"),
split("tag:x,1:abc", ""),
] {
assert_eq!(t, whole);
assert_eq!(s.hash_one(&t), s.hash_one(&whole));
assert_eq!(t.cmp(&whole), Ordering::Equal);
assert_eq!(t.to_string(), "tag:x,1:abc");
assert_eq!(t.len(), 11);
assert!(t == "tag:x,1:abc" && t != "tag:x,1:ab" && t != "tag:x,1:abcd");
}
assert_ne!(split("a", "b"), Tag::new("ac"));
assert_ne!(split("a", "b"), Tag::new("ab "));
assert!(split("a", "b") < Tag::new("ac"));
assert!(split("ab", "") > Tag::new("a"));
assert!(split("tag:x,", "1").starts_with("tag:x,1"));
assert!(!split("tag:x,", "1").starts_with("tag:x,12"));
assert!(split("tag:x,", "1").ends_with("x,1"));
assert!(split("tag:x,", "1").ends_with("1"));
assert!(split("tag:x,", "1").ends_with("tag:x,1"));
assert!(!split("tag:x,", "1").ends_with("!tag:x,1"));
assert!(!split("tag:x,", "1").ends_with("y,1"));
assert_eq!(format!("{:?}", split("!", "a")), "Tag(!a)");
}
#[test]
fn tag_str_pieces() {
let t = split("ab", "cd");
let p = t.parts();
assert_eq!(p.strip_prefix("a").unwrap().to_string(), "bcd");
assert_eq!(p.strip_prefix("abc").unwrap().to_string(), "d");
assert!(p.strip_prefix("abd").is_none());
assert!(p.strip_prefix("abcde").is_none());
assert_eq!(p.strip_prefix("abcd").unwrap().len(), 0);
assert_eq!(p.as_single(), None);
assert_eq!(p.strip_prefix("abc").unwrap().as_single(), Some("d"));
assert_eq!(p.chars().collect::<String>(), "abcd");
}
#[test]
fn digests_combine() {
let (hp, _) = digest("prefix-");
let (hs, pow) = digest("suffix");
let (hw, _) = digest("prefix-suffix");
assert_eq!(addmod(mulmod(hp, pow), hs), hw);
assert!(mulmod(P - 1, P - 1) < P);
}
}