1use super::{ToTokens, TokenStream};
2use crate::lex::{Cursor, LexError, Scan};
3use crate::{Span, TokenTree};
4
5#[macro_export]
44macro_rules! ident {
45 ($x:ident) => { $crate::Ident::lex(stringify!($x).to_string()).expect("invalid syntax") };
46 ($x:expr) => { $crate::Ident::lex($x).expect("invalid syntax") };
47 ($head:ident, $($tail:tt)+) => {{
48 let mut __ident = stringify!($head).to_string();
49 ident!(@accum __ident, $($tail)+)
50 }};
51 ($head:expr, $($tail:tt)+) => {{
52 let mut __ident = $head.to_string();
53 ident!(@accum __ident, $($tail)+)
54 }};
55 (@accum $acc:ident, $next:ident, $($tail:tt)+) => {{
56 $acc += stringify!($next);
57 ident!(@accum $acc, $($tail)+)
58 }};
59 (@accum $acc:ident, $next:expr, $($tail:tt)+) => {{
60 $acc += &$next.to_string();
61 ident!(@accum $acc, $($tail)+)
62 }};
63 (@accum $acc:ident, $last:ident) => {{
64 $acc += stringify!($last);
65 $crate::Ident::lex($acc).expect("invalid syntax")
66 }};
67 (@accum $acc:ident, $last:expr) => {{
68 $acc += &$last.to_string();
69 $crate::Ident::lex($acc).expect("invalid syntax")
70 }};
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Ident {
76 text: Box<str>,
77 span: Span,
78}
79
80impl Ident {
81 pub fn new(text: impl std::fmt::Display) -> Self {
82 Self {
83 text: text.to_string().into_boxed_str(),
84 span: Span::default(),
85 }
86 }
87
88 pub fn lex(input: impl std::fmt::Display) -> Result<Self, LexError> {
89 std::str::FromStr::from_str(&input.to_string())
90 }
91
92 #[inline]
93 pub fn with_span(mut self, span: Span) -> Self {
94 self.span = span;
95 self
96 }
97
98 #[inline]
99 pub fn text(&self) -> &str {
100 match self.text.strip_prefix("r#") {
101 Some(rest) => rest,
102 None => &self.text,
103 }
104 }
105
106 #[inline]
107 pub fn is_raw(&self) -> bool {
108 self.text.starts_with("r#")
109 }
110
111 #[inline]
112 pub fn span(&self) -> Span {
113 self.span
114 }
115
116 #[inline]
117 pub fn set_span(&mut self, span: Span) {
118 self.span = span;
119 }
120
121 pub fn to_lowercase(mut self) -> Self {
122 self.text = self.text.to_lowercase().into_boxed_str();
123 self
124 }
125
126 pub fn to_uppercase(mut self) -> Self {
127 self.text = self.text.to_uppercase().into_boxed_str();
128 self
129 }
130
131 pub fn to_pascal_case(&self) -> Self {
132 let text: String = self
133 .text
134 .split(|c: char| !c.is_alphanumeric())
135 .filter(|s| !s.is_empty())
136 .map(|word| {
137 let mut chars = word.chars();
138
139 match chars.next() {
140 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
141 None => String::new(),
142 }
143 })
144 .collect();
145
146 Self::new(text).with_span(self.span)
147 }
148
149 pub fn to_snake_case(&self) -> Self {
150 let mut output = String::with_capacity(self.text().len());
151
152 for (i, c) in self.text().chars().enumerate() {
153 if c.is_ascii_uppercase() {
154 if i > 0 && !output.ends_with('_') {
155 output.push('_');
156 }
157
158 output.push(c.to_ascii_lowercase());
159 } else if c.is_alphanumeric() {
160 output.push(c);
161 } else if !output.ends_with('_') {
162 output.push('_');
163 }
164 }
165
166 Self::new(output.trim_matches('_')).with_span(self.span)
167 }
168
169 #[inline]
170 pub fn to_token_tree(&self) -> TokenTree {
171 TokenTree::Ident(self.clone())
172 }
173
174 #[inline]
175 pub fn into_token_tree(self) -> TokenTree {
176 TokenTree::Ident(self)
177 }
178}
179
180impl Scan for Ident {
181 fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
182 if cursor.starts_with("r#") {
184 let after = cursor.advance_by(2);
185 let end = after.skip_while(unicode_ident::is_xid_continue);
186
187 if end.offset() == after.offset() {
188 return cursor.error().into();
189 }
190
191 let span = cursor.span_to(&end);
192 let name = &cursor.rest()[..end.offset() as usize - cursor.offset() as usize];
193 return Ok((end, Self::new(name).with_span(span)));
194 }
195
196 let first = cursor.first().ok_or(cursor.error())?;
197
198 if first != '_' && !unicode_ident::is_xid_start(first) {
199 return cursor.error().into();
200 }
201
202 let end = cursor.advance_by(first.len_utf8()).skip_while(unicode_ident::is_xid_continue);
203 let span = cursor.span_to(&end);
204 let text = &cursor.rest()[..end.offset() as usize - cursor.offset() as usize];
205 Ok((end, Self::new(text).with_span(span)))
206 }
207}
208
209impl std::fmt::Display for Ident {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 write!(f, "{}", self.text)
212 }
213}
214
215impl ToTokens for Ident {
216 fn to_tokens(&self, tokens: &mut TokenStream) {
217 tokens.extend_one(TokenTree::Ident(self.clone()));
218 }
219}
220
221impl crate::Spanner for Ident {
222 fn span(&self) -> Span {
223 self.span
224 }
225}
226
227impl std::str::FromStr for Ident {
228 type Err = LexError;
229
230 fn from_str(s: &str) -> Result<Self, Self::Err> {
231 let cursor = Cursor::new(s, 0);
232 let (_, ident) = Self::scan(cursor)?;
233 Ok(ident)
234 }
235}
236
237#[cfg(feature = "serde")]
238impl serde::Serialize for Ident {
239 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
240 where
241 S: serde::Serializer,
242 {
243 self.text.serialize(s)
244 }
245}
246
247impl PartialEq<str> for Ident {
248 fn eq(&self, other: &str) -> bool {
249 self.text.as_ref() == other
250 }
251}
252
253impl PartialEq<&str> for Ident {
254 fn eq(&self, other: &&str) -> bool {
255 self.text.as_ref() == *other
256 }
257}
258
259impl std::hash::Hash for Ident {
260 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
261 self.text.hash(state);
262 }
263}