css_lexer/atom_set.rs
1/// Object-safe version of AtomSet for use with trait objects. This trait mirrors the functionality of AtomSet but is
2/// compatible with `dyn` trait objects.
3pub trait DynAtomSet: std::fmt::Debug {
4 /// Converts a string keyword to the corresponding atom variant, returning its bit representation.
5 fn str_to_bits(&self, keyword: &str) -> u32;
6
7 /// Converts this atom's bit representation back to its string representation.
8 fn bits_to_str(&self, bits: u32) -> &'static str;
9
10 /// Get the current bits of this Atom.
11 fn bits(&self) -> u32;
12}
13
14/// # Usage with `#[derive(AtomSet)]`
15///
16/// The easiest way to implement this trait is using the `AtomSet` derive macro:
17///
18/// ```rust
19/// use derive_atom_set::AtomSet;
20/// use css_lexer::AtomSet;
21///
22/// #[derive(Debug, Default, Copy, Clone, PartialEq, AtomSet)]
23/// pub enum Units {
24/// // AtomSet must derive default, ideally with an empty atom, or equivalent!
25/// #[default]
26/// _None,
27///
28/// // Automatically converts to "px"
29/// Px,
30///
31/// // Automatically converts to "rem"
32/// Rem,
33///
34/// // Custom string mapping
35/// #[atom("%")]
36/// Percent,
37/// }
38/// ```
39pub trait AtomSet: Default + std::fmt::Debug {
40 /// Converts a string keyword to the corresponding atom variant.
41 ///
42 /// This performs case-insensitive matching and returns the `Empty` variant for unrecognized strings.
43 ///
44 /// # Examples
45 ///
46 /// ```rust
47 /// # use css_lexer::{AtomSet};
48 /// use derive_atom_set::*;
49 ///
50 /// #[derive(Debug, Default, Copy, Clone, PartialEq, AtomSet)]
51 /// enum MyAtomSet {
52 /// #[default]
53 /// _None,
54 /// Url
55 /// }
56 /// assert_eq!(MyAtomSet::from_str("url"), MyAtomSet::Url);
57 /// assert_eq!(MyAtomSet::from_str("URL"), MyAtomSet::Url); // Case insensitive
58 /// assert_eq!(MyAtomSet::from_str("unknown"), MyAtomSet::_None);
59 /// ```
60 fn from_str(keyword: &str) -> Self;
61
62 /// Converts this atom back to its string representation.
63 ///
64 /// Returns a static string slice that represents this atom's canonical form.
65 ///
66 /// The variant marked `#[default]` will always return the empty string.
67 ///
68 /// # Examples
69 ///
70 /// ```rust
71 /// # use css_lexer::AtomSet;
72 /// use derive_atom_set::*;
73 ///
74 /// #[derive(Debug, Default, Copy, Clone, PartialEq, AtomSet)]
75 /// enum MyAtomSet {
76 /// #[default]
77 /// _None,
78 /// Url
79 /// }
80 /// assert_eq!(MyAtomSet::Url.to_str(), "url");
81 /// assert_eq!(MyAtomSet::_None.to_str(), "");
82 ///
83 /// // Round-trip conversion
84 /// let atom = MyAtomSet::from_str("url");
85 /// assert_eq!(atom.to_str(), "url");
86 /// ```
87 fn to_str(self) -> &'static str;
88
89 /// Returns the length in characters of this atom's string representation.
90 ///
91 /// This is equivalent to `self.to_str().len()` but may be more efficient depending on the implementation.
92 ///
93 /// # Examples
94 ///
95 /// ```rust
96 /// # use css_lexer::AtomSet;
97 /// use derive_atom_set::*;
98 ///
99 /// #[derive(Debug, Default, Copy, Clone, PartialEq, AtomSet)]
100 /// enum MyAtomSet {
101 /// #[default]
102 /// _None,
103 /// Url
104 /// }
105 /// assert_eq!(MyAtomSet::Url.len(), 3);
106 /// assert_eq!(MyAtomSet::_None.len(), 0);
107 /// ```
108 fn len(&self) -> u32;
109
110 /// Returns true if the length of this atom is 0.
111 ///
112 /// This is equivalent to `self.to_str().is_empty()` but may be more efficient depending on the implementation.
113 ///
114 /// # Examples
115 ///
116 /// ```rust
117 /// # use css_lexer::AtomSet;
118 /// use derive_atom_set::*;
119 ///
120 /// #[derive(Debug, Default, Copy, Clone, PartialEq, AtomSet)]
121 /// enum MyAtomSet {
122 /// #[default]
123 /// _None,
124 /// Url
125 /// }
126 /// assert!(!MyAtomSet::Url.is_empty());
127 /// assert!(MyAtomSet::_None.is_empty());
128 /// ```
129 fn is_empty(&self) -> bool {
130 self.len() == 0
131 }
132
133 /// Converts a numeric bit representation back to an atom variant.
134 ///
135 /// This is used internally for efficient storage and retrieval. Returns the `Empty` variant for unrecognized bit
136 /// values.
137 ///
138 /// # Examples
139 ///
140 /// ```rust
141 /// # use css_lexer::AtomSet;
142 /// use derive_atom_set::*;
143 ///
144 /// #[derive(Debug, Default, Copy, Clone, PartialEq, AtomSet)]
145 /// enum MyAtomSet {
146 /// #[default]
147 /// _None,
148 /// Url
149 /// }
150 /// let atom = MyAtomSet::Url;
151 /// let bits = atom.as_bits();
152 /// let restored = MyAtomSet::from_bits(bits);
153 /// assert_eq!(atom, restored);
154 /// ```
155 fn from_bits(bits: u32) -> Self;
156
157 /// Converts this atom to its numeric bit representation.
158 ///
159 /// This is used internally for efficient storage. The bit value corresponds to the enum discriminant.
160 ///
161 /// # Examples
162 ///
163 /// ```rust
164 /// # use css_lexer::AtomSet;
165 /// use derive_atom_set::*;
166 ///
167 /// #[derive(Debug, Default, Copy, Clone, PartialEq, AtomSet)]
168 /// enum MyAtomSet {
169 /// #[default]
170 /// _None,
171 /// Url
172 /// }
173 /// let bits = MyAtomSet::Url.as_bits();
174 /// assert_eq!(MyAtomSet::from_bits(bits), MyAtomSet::Url);
175 /// ```
176 fn as_bits(&self) -> u32;
177}
178
179/// Blanket implementation so any AtomSet can be used as a DynAtomSet
180impl<T: AtomSet + Clone + 'static> DynAtomSet for T {
181 fn str_to_bits(&self, keyword: &str) -> u32 {
182 T::from_str(keyword).as_bits()
183 }
184
185 fn bits_to_str(&self, bits: u32) -> &'static str {
186 T::from_bits(bits).to_str()
187 }
188
189 fn bits(&self) -> u32 {
190 self.clone().as_bits()
191 }
192}