use std::fmt;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use serde::{Deserialize, Serialize};
static SCOPE_COUNTER: AtomicU32 = AtomicU32::new(1);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ScopeTag(u32);
impl ScopeTag {
#[must_use]
pub fn fresh() -> Self {
Self(SCOPE_COUNTER.fetch_add(1, Ordering::Relaxed))
}
#[must_use]
pub const fn from_raw(raw: u32) -> Self {
Self(raw)
}
pub const LEGACY: Self = Self(0);
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Ident {
pub scope: ScopeTag,
pub index: u32,
pub name: Arc<str>,
}
impl PartialEq for Ident {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.scope == other.scope && self.index == other.index
}
}
impl Eq for Ident {}
impl std::hash::Hash for Ident {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.scope.hash(state);
self.index.hash(state);
}
}
impl PartialOrd for Ident {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Ident {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.scope
.cmp(&other.scope)
.then(self.index.cmp(&other.index))
}
}
impl Ident {
#[must_use]
pub fn new(scope: ScopeTag, index: u32, name: impl Into<Arc<str>>) -> Self {
Self {
scope,
index,
name: name.into(),
}
}
#[must_use]
pub fn renamed(&self, new_name: impl Into<Arc<str>>) -> Self {
Self {
scope: self.scope,
index: self.index,
name: new_name.into(),
}
}
#[must_use]
pub fn from_legacy(name: impl Into<Arc<str>>) -> Self {
let name: Arc<str> = name.into();
let index = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
name.hash(&mut hasher);
#[allow(clippy::cast_possible_truncation)]
{
hasher.finish() as u32
}
};
Self {
scope: ScopeTag::LEGACY,
index,
name,
}
}
}
impl fmt::Display for Ident {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.name)
}
}
impl AsRef<str> for Ident {
fn as_ref(&self) -> &str {
&self.name
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Name(pub Arc<str>);
impl PartialEq for Name {
#[inline]
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0) || *self.0 == *other.0
}
}
impl Eq for Name {}
impl std::hash::Hash for Name {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl PartialOrd for Name {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Name {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl Name {
#[must_use]
pub fn new(s: impl Into<Arc<str>>) -> Self {
Self(s.into())
}
#[must_use]
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<String> for Name {
fn from(s: String) -> Self {
Self(Arc::from(s))
}
}
impl From<&str> for Name {
fn from(s: &str) -> Self {
Self(Arc::from(s))
}
}
impl From<Arc<str>> for Name {
fn from(s: Arc<str>) -> Self {
Self(s)
}
}
impl PartialEq<str> for Name {
fn eq(&self, other: &str) -> bool {
&*self.0 == other
}
}
impl<'a> PartialEq<&'a str> for Name {
fn eq(&self, other: &&'a str) -> bool {
&*self.0 == *other
}
}
impl std::ops::Deref for Name {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl std::borrow::Borrow<str> for Name {
fn borrow(&self) -> &str {
&self.0
}
}
impl Default for Name {
fn default() -> Self {
Self(Arc::from(""))
}
}
impl From<Name> for String {
fn from(n: Name) -> Self {
n.0.to_string()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NameSite {
EdgeLabel,
VertexId,
VertexKind,
EdgeKind,
Nsid,
ConstraintSort,
InstanceAnchor,
TheoryName,
SortName,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SiteRename {
pub site: NameSite,
pub old: Arc<str>,
pub new: Arc<str>,
}
impl SiteRename {
#[must_use]
pub fn new(site: NameSite, old: impl Into<Arc<str>>, new: impl Into<Arc<str>>) -> Self {
Self {
site,
old: old.into(),
new: new.into(),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn ident_equality_ignores_name() {
let scope = ScopeTag::fresh();
let a = Ident::new(scope, 0, "Vertex");
let b = Ident::new(scope, 0, "Node");
assert_eq!(
a, b,
"same (scope, index) should be equal regardless of name"
);
}
#[test]
fn ident_inequality_different_scope() {
let a = Ident::new(ScopeTag::fresh(), 0, "Vertex");
let b = Ident::new(ScopeTag::fresh(), 0, "Vertex");
assert_ne!(
a, b,
"different scopes should be unequal even with same name"
);
}
#[test]
fn ident_inequality_different_index() {
let scope = ScopeTag::fresh();
let a = Ident::new(scope, 0, "Vertex");
let b = Ident::new(scope, 1, "Vertex");
assert_ne!(a, b, "different indices should be unequal");
}
#[test]
fn ident_hash_consistency() {
use std::collections::HashMap;
let scope = ScopeTag::fresh();
let key = Ident::new(scope, 42, "original");
let mut map = HashMap::new();
map.insert(key.clone(), "value");
let renamed = key.renamed("renamed");
assert_eq!(map.get(&renamed), Some(&"value"));
}
#[test]
fn ident_renamed_preserves_identity() {
let scope = ScopeTag::fresh();
let a = Ident::new(scope, 5, "old_name");
let b = a.renamed("new_name");
assert_eq!(a, b);
assert_eq!(b.name.as_ref(), "new_name");
}
#[test]
fn ident_from_legacy_is_deterministic() {
let a = Ident::from_legacy("post:body.text");
let b = Ident::from_legacy("post:body.text");
assert_eq!(a, b);
assert_eq!(a.scope, ScopeTag::LEGACY);
assert_eq!(a.name.as_ref(), "post:body.text");
}
#[test]
fn ident_from_legacy_different_names_differ() {
let a = Ident::from_legacy("post:body.text");
let b = Ident::from_legacy("post:body.content");
assert_ne!(a, b);
}
#[test]
fn name_ptr_eq_fast_path() {
let arc: Arc<str> = Arc::from("hello");
let a = Name(Arc::clone(&arc));
let b = Name(Arc::clone(&arc));
assert_eq!(a, b);
}
#[test]
fn name_string_eq_fallback() {
let a = Name::from("hello");
let b = Name::from(String::from("hello"));
assert_eq!(a, b);
}
#[test]
fn name_from_conversions() {
let from_str: Name = "hello".into();
let from_string: Name = String::from("hello").into();
let from_arc: Name = Arc::<str>::from("hello").into();
assert_eq!(from_str, from_string);
assert_eq!(from_string, from_arc);
}
#[test]
fn name_partial_eq_str() {
let name = Name::from("test");
assert!(name == "test");
assert!(name == "test");
}
#[test]
fn name_display() {
let name = Name::from("my_field");
assert_eq!(format!("{name}"), "my_field");
}
#[test]
fn name_ordering() {
let a = Name::from("alpha");
let b = Name::from("beta");
assert!(a < b);
}
#[test]
fn site_rename_construction() {
let rename = SiteRename::new(NameSite::EdgeLabel, "text", "body");
assert_eq!(rename.site, NameSite::EdgeLabel);
assert_eq!(rename.old.as_ref(), "text");
assert_eq!(rename.new.as_ref(), "body");
}
#[test]
fn ident_display() {
let scope = ScopeTag::fresh();
let id = Ident::new(scope, 0, "Vertex");
assert_eq!(format!("{id}"), "Vertex");
}
#[test]
fn ident_serde_roundtrip() {
let scope = ScopeTag::fresh();
let id = Ident::new(scope, 42, "test_sort");
let json = serde_json::to_string(&id).unwrap();
let restored: Ident = serde_json::from_str(&json).unwrap();
assert_eq!(id, restored);
assert_eq!(id.name, restored.name);
}
#[test]
fn name_serde_roundtrip() {
let name = Name::from("field_name");
let json = serde_json::to_string(&name).unwrap();
assert_eq!(json, "\"field_name\""); let restored: Name = serde_json::from_str(&json).unwrap();
assert_eq!(name, restored);
}
#[test]
fn site_rename_serde_roundtrip() {
let rename = SiteRename::new(NameSite::VertexKind, "string", "text");
let json = serde_json::to_string(&rename).unwrap();
let restored: SiteRename = serde_json::from_str(&json).unwrap();
assert_eq!(rename, restored);
}
}