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/// ```ignore
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/// A Rust identifier token, including its source span and raw-identifier form.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Ident {
76    text: Box<str>,
77    span: Span,
78}
79
80impl Ident {
81    #[inline]
82    pub fn new(text: impl std::fmt::Display) -> Self {
83        Self {
84            text: text.to_string().into_boxed_str(),
85            span: Span::default(),
86        }
87    }
88
89    #[inline]
90    pub fn lex(input: impl std::fmt::Display) -> Result<Self, LexError> {
91        std::str::FromStr::from_str(&input.to_string())
92    }
93
94    #[inline]
95    pub fn with_span(mut self, span: Span) -> Self {
96        self.span = span;
97        self
98    }
99
100    #[inline]
101    pub fn text(&self) -> &str {
102        match self.text.strip_prefix("r#") {
103            Some(rest) => rest,
104            None => &self.text,
105        }
106    }
107
108    #[inline]
109    pub fn is_raw(&self) -> bool {
110        self.text.starts_with("r#")
111    }
112
113    #[inline]
114    pub fn span(&self) -> Span {
115        self.span
116    }
117
118    #[inline]
119    pub fn set_span(&mut self, span: Span) {
120        self.span = span;
121    }
122
123    #[inline]
124    pub fn to_lowercase(mut self) -> Self {
125        self.text = self.text.to_lowercase().into_boxed_str();
126        self
127    }
128
129    #[inline]
130    pub fn to_uppercase(mut self) -> Self {
131        self.text = self.text.to_uppercase().into_boxed_str();
132        self
133    }
134
135    #[inline]
136    pub fn to_token_tree(&self) -> TokenTree {
137        TokenTree::Ident(self.clone())
138    }
139
140    #[inline]
141    pub fn into_token_tree(self) -> TokenTree {
142        TokenTree::Ident(self)
143    }
144}
145
146impl Scan for Ident {
147    fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
148        // Raw ident: r#ident
149        if cursor.starts_with("r#") {
150            let after = cursor.advance_by(2);
151            let end = after.skip_while(unicode_ident::is_xid_continue);
152
153            if end.offset() == after.offset() {
154                return cursor.error().into();
155            }
156
157            let span = cursor.span_to(&end);
158            let name = &cursor.rest()[..end.offset() as usize - cursor.offset() as usize];
159            return Ok((end, Self::new(name).with_span(span)));
160        }
161
162        let first = cursor.first().ok_or(cursor.error())?;
163
164        if first != '_' && !unicode_ident::is_xid_start(first) {
165            return cursor.error().into();
166        }
167
168        let end = cursor.advance_by(first.len_utf8()).skip_while(unicode_ident::is_xid_continue);
169        let span = cursor.span_to(&end);
170        let text = &cursor.rest()[..end.offset() as usize - cursor.offset() as usize];
171        Ok((end, Self::new(text).with_span(span)))
172    }
173}
174
175impl std::fmt::Display for Ident {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        write!(f, "{}", self.text)
178    }
179}
180
181impl ToTokens for Ident {
182    fn to_tokens(&self, tokens: &mut TokenStream) {
183        tokens.extend_one(TokenTree::Ident(self.clone()));
184    }
185}
186
187impl crate::Spanner for Ident {
188    fn span(&self) -> Span {
189        self.span
190    }
191}
192
193impl std::str::FromStr for Ident {
194    type Err = LexError;
195
196    fn from_str(s: &str) -> Result<Self, Self::Err> {
197        let cursor = Cursor::new(s, 0);
198        let (_, ident) = Self::scan(cursor)?;
199        Ok(ident)
200    }
201}
202
203#[cfg(feature = "serde")]
204impl serde::Serialize for Ident {
205    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
206    where
207        S: serde::Serializer,
208    {
209        self.text.serialize(s)
210    }
211}
212
213impl PartialEq<str> for Ident {
214    fn eq(&self, other: &str) -> bool {
215        self.text.as_ref() == other
216    }
217}
218
219impl PartialEq<&str> for Ident {
220    fn eq(&self, other: &&str) -> bool {
221        self.text.as_ref() == *other
222    }
223}
224
225impl std::hash::Hash for Ident {
226    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
227        self.text.hash(state);
228    }
229}