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    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    #[inline]
132    pub fn to_token_tree(&self) -> TokenTree {
133        TokenTree::Ident(self.clone())
134    }
135
136    #[inline]
137    pub fn into_token_tree(self) -> TokenTree {
138        TokenTree::Ident(self)
139    }
140}
141
142impl Scan for Ident {
143    fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
144        // Raw ident: r#ident
145        if cursor.starts_with("r#") {
146            let after = cursor.advance_by(2);
147            let end = after.skip_while(unicode_ident::is_xid_continue);
148
149            if end.offset() == after.offset() {
150                return cursor.error().into();
151            }
152
153            let span = cursor.span_to(&end);
154            let name = &cursor.rest()[..end.offset() as usize - cursor.offset() as usize];
155            return Ok((end, Self::new(name).with_span(span)));
156        }
157
158        let first = cursor.first().ok_or(cursor.error())?;
159
160        if first != '_' && !unicode_ident::is_xid_start(first) {
161            return cursor.error().into();
162        }
163
164        let end = cursor.advance_by(first.len_utf8()).skip_while(unicode_ident::is_xid_continue);
165        let span = cursor.span_to(&end);
166        let text = &cursor.rest()[..end.offset() as usize - cursor.offset() as usize];
167        Ok((end, Self::new(text).with_span(span)))
168    }
169}
170
171impl std::fmt::Display for Ident {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        write!(f, "{}", self.text)
174    }
175}
176
177impl ToTokens for Ident {
178    fn to_tokens(&self, tokens: &mut TokenStream) {
179        tokens.extend_one(TokenTree::Ident(self.clone()));
180    }
181}
182
183impl crate::Spanner for Ident {
184    fn span(&self) -> Span {
185        self.span
186    }
187}
188
189impl std::str::FromStr for Ident {
190    type Err = LexError;
191
192    fn from_str(s: &str) -> Result<Self, Self::Err> {
193        let cursor = Cursor::new(s, 0);
194        let (_, ident) = Self::scan(cursor)?;
195        Ok(ident)
196    }
197}
198
199#[cfg(feature = "serde")]
200impl serde::Serialize for Ident {
201    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
202    where
203        S: serde::Serializer,
204    {
205        self.text.serialize(s)
206    }
207}
208
209impl PartialEq<str> for Ident {
210    fn eq(&self, other: &str) -> bool {
211        self.text.as_ref() == other
212    }
213}
214
215impl PartialEq<&str> for Ident {
216    fn eq(&self, other: &&str) -> bool {
217        self.text.as_ref() == *other
218    }
219}
220
221impl std::hash::Hash for Ident {
222    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
223        self.text.hash(state);
224    }
225}