cirru_edn/tag.rs
1//! Tag abstractions for EDN values.
2//!
3//! Tags (previously called "keywords") are named constants that can be used
4//! as map keys, enum values, or identifiers. This module provides efficient
5//! string reuse through Arc<str> to minimize memory allocation.
6
7use std::{
8 cmp::Eq,
9 cmp::Ordering,
10 fmt,
11 hash::{Hash, Hasher},
12 sync::Arc,
13};
14
15/// Tags across whole program with strings reused for efficiency.
16///
17/// A tag is similar to a keyword in other Lisp dialects - it's a
18/// self-evaluating named constant. Tags are commonly used as:
19/// - Map keys (like `:name`, `:age`)
20/// - Enum-like values (like `:success`, `:error`)
21/// - Type identifiers in records
22///
23/// # Examples
24///
25/// ```
26/// use cirru_edn::EdnTag;
27///
28/// let tag1 = EdnTag::new("status");
29/// let tag2 = EdnTag::from("active");
30/// ```
31#[derive(fmt::Debug, Clone)]
32pub struct EdnTag(
33 /// The tag string - there will be a practical limit on the count of all tags
34 pub Arc<str>,
35);
36
37impl fmt::Display for EdnTag {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.write_str(&self.0)
40 }
41}
42
43impl Hash for EdnTag {
44 fn hash<H>(&self, _state: &mut H)
45 where
46 H: Hasher,
47 {
48 "EdnTag:".hash(_state);
49 self.0.hash(_state);
50 }
51}
52
53impl From<&str> for EdnTag {
54 fn from(s: &str) -> Self {
55 Self(Arc::from(s))
56 }
57}
58
59impl EdnTag {
60 /// Create a new tag from a string.
61 ///
62 /// # Examples
63 ///
64 /// ```
65 /// use cirru_edn::EdnTag;
66 ///
67 /// let tag = EdnTag::new("my-tag");
68 /// assert_eq!(tag.ref_str(), "my-tag");
69 /// ```
70 pub fn new<T: Into<Arc<str>>>(s: T) -> Self {
71 EdnTag(s.into())
72 }
73
74 /// Get the inner Arc<str> reference.
75 ///
76 /// This provides access to the underlying string data without cloning.
77 pub fn arc_str(&self) -> Arc<str> {
78 (*self.0).into()
79 }
80
81 /// Get a string slice reference for comparison.
82 ///
83 /// This is the most efficient way to compare tag content.
84 pub fn ref_str(&self) -> &str {
85 &self.0
86 }
87
88 /// Check if the tag matches a string slice.
89 ///
90 /// This is more efficient than converting the tag to a string.
91 ///
92 /// # Examples
93 ///
94 /// ```
95 /// use cirru_edn::EdnTag;
96 ///
97 /// let tag = EdnTag::new("status");
98 /// assert!(tag.matches("status"));
99 /// assert!(!tag.matches("other"));
100 /// ```
101 pub fn matches(&self, s: &str) -> bool {
102 self.0.as_ref() == s
103 }
104
105 /// Get the length of the tag string.
106 pub fn len(&self) -> usize {
107 self.0.len()
108 }
109
110 /// Check if the tag is empty.
111 pub fn is_empty(&self) -> bool {
112 self.0.is_empty()
113 }
114}
115
116impl Ord for EdnTag {
117 fn cmp(&self, other: &Self) -> Ordering {
118 self.0.cmp(&other.0)
119 }
120}
121
122impl PartialOrd for EdnTag {
123 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
124 Some(self.cmp(other))
125 }
126}
127
128impl Eq for EdnTag {}
129
130impl PartialEq for EdnTag {
131 fn eq(&self, other: &Self) -> bool {
132 self.0 == other.0
133 }
134}