Skip to main content

moxy_token/
ident.rs

1use super::{ToTokens, TokenStream};
2use crate::lex::{Cursor, LexError, Scan};
3use crate::{Span, TokenTree};
4
5/// Builds an [`Ident`] from one or more segments, lexing the result and panicking
6/// on invalid identifier syntax.
7///
8/// Accepts a bare identifier, a string/expression that evaluates to a name, or a
9/// comma-separated list of segments that are concatenated into a single identifier.
10/// Bare identifiers are captured by name via `stringify!`; expressions are converted
11/// with [`ToString`].
12///
13/// # Examples
14///
15/// ```
16/// use moxy_token::ident;
17///
18/// // Bare identifier
19/// let counter = ident!(counter);
20/// assert_eq!(counter, "counter");
21///
22/// // Raw identifier
23/// let kw = ident!(r#struct);
24/// assert_eq!(kw.text(), "struct");
25/// assert!(kw.is_raw());
26///
27/// // From a string or expression
28/// let name = ident!("buffer");
29/// assert_eq!(name, "buffer");
30///
31/// // Concatenate segments into one identifier
32/// let field = ident!(get, "_", value);
33/// assert_eq!(field, "get_value");
34///
35/// // Bare identifier segments are taken by name, not evaluated
36/// let slot = ident!(slot, "_", n);
37/// assert_eq!(slot, "slot_n");
38/// ```
39///
40/// # Panics
41///
42/// Panics if the concatenated text is not a valid identifier.
43#[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)]
74pub struct Ident {
75    text: Box<str>,
76    span: Span,
77}
78
79impl Ident {
80    #[inline]
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    #[inline]
89    pub fn lex(input: impl std::fmt::Display) -> Result<Self, LexError> {
90        std::str::FromStr::from_str(&input.to_string())
91    }
92
93    #[inline]
94    pub fn with_span(mut self, span: Span) -> Self {
95        self.span = span;
96        self
97    }
98
99    #[inline]
100    pub fn text(&self) -> &str {
101        match self.text.strip_prefix("r#") {
102            Some(rest) => rest,
103            None => &self.text,
104        }
105    }
106
107    #[inline]
108    pub fn is_raw(&self) -> bool {
109        self.text.starts_with("r#")
110    }
111
112    #[inline]
113    pub fn span(&self) -> Span {
114        self.span
115    }
116
117    #[inline]
118    pub fn set_span(&mut self, span: Span) {
119        self.span = span;
120    }
121
122    #[inline]
123    pub fn to_lowercase(mut self) -> Self {
124        self.text = self.text.to_lowercase().into_boxed_str();
125        self
126    }
127
128    #[inline]
129    pub fn to_uppercase(mut self) -> Self {
130        self.text = self.text.to_uppercase().into_boxed_str();
131        self
132    }
133
134    #[inline]
135    pub fn to_token_tree(&self) -> TokenTree {
136        TokenTree::Ident(self.clone())
137    }
138
139    #[inline]
140    pub fn into_token_tree(self) -> TokenTree {
141        TokenTree::Ident(self)
142    }
143}
144
145impl Scan for Ident {
146    fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
147        // Raw ident: r#ident
148        if cursor.starts_with("r#") {
149            let after = cursor.advance_by(2);
150            let end = after.skip_while(unicode_ident::is_xid_continue);
151
152            if end.offset() == after.offset() {
153                return cursor.error().into();
154            }
155
156            let span = cursor.span_to(&end);
157            let name = &cursor.rest()[..end.offset() as usize - cursor.offset() as usize];
158            return Ok((end, Self::new(name).with_span(span)));
159        }
160
161        let first = cursor.first().ok_or(cursor.error())?;
162
163        if first != '_' && !unicode_ident::is_xid_start(first) {
164            return cursor.error().into();
165        }
166
167        let end = cursor.advance_by(first.len_utf8()).skip_while(unicode_ident::is_xid_continue);
168        let span = cursor.span_to(&end);
169        let text = &cursor.rest()[..end.offset() as usize - cursor.offset() as usize];
170        Ok((end, Self::new(text).with_span(span)))
171    }
172}
173
174impl std::fmt::Display for Ident {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        write!(f, "{}", self.text)
177    }
178}
179
180impl ToTokens for Ident {
181    fn to_tokens(&self, tokens: &mut TokenStream) {
182        tokens.extend_one(TokenTree::Ident(self.clone()));
183    }
184}
185
186impl crate::Spanner for Ident {
187    fn span(&self) -> Span {
188        self.span
189    }
190}
191
192impl std::str::FromStr for Ident {
193    type Err = LexError;
194
195    fn from_str(s: &str) -> Result<Self, Self::Err> {
196        let cursor = Cursor::new(s, 0);
197        let (_, ident) = Self::scan(cursor)?;
198        Ok(ident)
199    }
200}
201
202#[cfg(feature = "serde")]
203impl serde::Serialize for Ident {
204    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
205    where
206        S: serde::Serializer,
207    {
208        self.text.serialize(s)
209    }
210}
211
212impl PartialEq<str> for Ident {
213    fn eq(&self, other: &str) -> bool {
214        self.text.as_ref() == other
215    }
216}
217
218impl PartialEq<&str> for Ident {
219    fn eq(&self, other: &&str) -> bool {
220        self.text.as_ref() == *other
221    }
222}
223
224impl std::hash::Hash for Ident {
225    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
226        self.text.hash(state);
227    }
228}