Skip to content

Commit 1637a8a

Browse files
committed
Add quote macro
1 parent c46768d commit 1637a8a

File tree

2 files changed

+262
-0
lines changed

2 files changed

+262
-0
lines changed

crates/ra_hir_expand/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod name;
1111
pub mod hygiene;
1212
pub mod diagnostics;
1313
pub mod builtin_macro;
14+
pub mod quote;
1415

1516
use std::hash::{Hash, Hasher};
1617
use std::sync::Arc;

crates/ra_hir_expand/src/quote.rs

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
//! A simplified version of quote-crate like quasi quote macro
2+
3+
// A helper macro quote macro
4+
// FIXME:
5+
// 1. Not all puncts are handled
6+
// 2. #()* pattern repetition not supported now
7+
// * But we can do it manually, see `test_quote_derive_copy_hack`
8+
#[doc(hidden)]
9+
#[macro_export]
10+
macro_rules! __quote {
11+
() => {
12+
Vec::<tt::TokenTree>::new()
13+
};
14+
15+
( @SUBTREE $delim:ident $($tt:tt)* ) => {
16+
{
17+
let children = $crate::__quote!($($tt)*);
18+
let subtree = tt::Subtree {
19+
delimiter: tt::Delimiter::$delim,
20+
token_trees: $crate::quote::IntoTt::to_tokens(children),
21+
};
22+
subtree
23+
}
24+
};
25+
26+
( @PUNCT $first:literal ) => {
27+
{
28+
vec![
29+
tt::Leaf::Punct(tt::Punct {
30+
char: $first,
31+
spacing: tt::Spacing::Alone,
32+
}).into()
33+
]
34+
}
35+
};
36+
37+
( @PUNCT $first:literal, $sec:literal ) => {
38+
{
39+
vec![
40+
tt::Leaf::Punct(tt::Punct {
41+
char: $first,
42+
spacing: tt::Spacing::Joint,
43+
}).into(),
44+
tt::Leaf::Punct(tt::Punct {
45+
char: $sec,
46+
spacing: tt::Spacing::Alone,
47+
}).into()
48+
]
49+
}
50+
};
51+
52+
// hash variable
53+
( # $first:ident $($tail:tt)* ) => {
54+
{
55+
let token = $crate::quote::ToTokenTree::to_token($first);
56+
let mut tokens = vec![token.into()];
57+
let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
58+
tokens.append(&mut tail_tokens);
59+
tokens
60+
}
61+
};
62+
63+
// Brace
64+
( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) };
65+
// Bracket
66+
( [ $($tt:tt)* ] ) => { $crate::__quote!(@SUBTREE Bracket $($tt)*) };
67+
// Parenthesis
68+
( ( $($tt:tt)* ) ) => { $crate::__quote!(@SUBTREE Parenthesis $($tt)*) };
69+
70+
// Literal
71+
( $tt:literal ) => { vec![$crate::quote::ToTokenTree::to_token($tt).into()] };
72+
// Ident
73+
( $tt:ident ) => {
74+
vec![ {
75+
tt::Leaf::Ident(tt::Ident {
76+
text: stringify!($tt).into(),
77+
id: tt::TokenId::unspecified(),
78+
}).into()
79+
}]
80+
};
81+
82+
// Puncts
83+
// FIXME: Not all puncts are handled
84+
( -> ) => {$crate::__quote!(@PUNCT '-', '>')};
85+
( & ) => {$crate::__quote!(@PUNCT '&')};
86+
( , ) => {$crate::__quote!(@PUNCT ',')};
87+
( : ) => {$crate::__quote!(@PUNCT ':')};
88+
( . ) => {$crate::__quote!(@PUNCT '.')};
89+
90+
( $first:tt $($tail:tt)+ ) => {
91+
{
92+
let mut tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($first));
93+
let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
94+
95+
tokens.append(&mut tail_tokens);
96+
tokens
97+
}
98+
};
99+
}
100+
101+
/// FIXME:
102+
/// It probably should implement in proc-macro
103+
#[macro_export]
104+
macro_rules! quote {
105+
( $($tt:tt)* ) => {
106+
$crate::quote::IntoTt::to_subtree($crate::__quote!($($tt)*))
107+
}
108+
}
109+
110+
pub(crate) trait IntoTt {
111+
fn to_subtree(self) -> tt::Subtree;
112+
fn to_tokens(self) -> Vec<tt::TokenTree>;
113+
}
114+
115+
impl IntoTt for Vec<tt::TokenTree> {
116+
fn to_subtree(self) -> tt::Subtree {
117+
tt::Subtree { delimiter: tt::Delimiter::None, token_trees: self }
118+
}
119+
120+
fn to_tokens(self) -> Vec<tt::TokenTree> {
121+
self
122+
}
123+
}
124+
125+
impl IntoTt for tt::Subtree {
126+
fn to_subtree(self) -> tt::Subtree {
127+
self
128+
}
129+
130+
fn to_tokens(self) -> Vec<tt::TokenTree> {
131+
vec![tt::TokenTree::Subtree(self)]
132+
}
133+
}
134+
135+
pub(crate) trait ToTokenTree {
136+
fn to_token(self) -> tt::TokenTree;
137+
}
138+
139+
impl ToTokenTree for tt::TokenTree {
140+
fn to_token(self) -> tt::TokenTree {
141+
self
142+
}
143+
}
144+
145+
impl ToTokenTree for tt::Subtree {
146+
fn to_token(self) -> tt::TokenTree {
147+
self.into()
148+
}
149+
}
150+
151+
macro_rules! impl_to_to_tokentrees {
152+
($($ty:ty => $this:ident $im:block);*) => {
153+
$(
154+
impl ToTokenTree for $ty {
155+
fn to_token($this) -> tt::TokenTree {
156+
let leaf: tt::Leaf = $im.into();
157+
leaf.into()
158+
}
159+
}
160+
161+
impl ToTokenTree for &$ty {
162+
fn to_token($this) -> tt::TokenTree {
163+
let leaf: tt::Leaf = $im.clone().into();
164+
leaf.into()
165+
}
166+
}
167+
)*
168+
}
169+
}
170+
171+
impl_to_to_tokentrees! {
172+
u32 => self { tt::Literal{text: self.to_string().into()} };
173+
usize => self { tt::Literal{text: self.to_string().into()}};
174+
i32 => self { tt::Literal{text: self.to_string().into()}};
175+
&str => self { tt::Literal{text: self.to_string().into()}};
176+
String => self { tt::Literal{text: self.into()}};
177+
tt::Leaf => self { self };
178+
tt::Literal => self { self };
179+
tt::Ident => self { self };
180+
tt::Punct => self { self }
181+
}
182+
183+
#[cfg(test)]
184+
mod tests {
185+
#[test]
186+
fn test_quote_delimiters() {
187+
assert_eq!(quote!({}).to_string(), "{}");
188+
assert_eq!(quote!(()).to_string(), "()");
189+
assert_eq!(quote!([]).to_string(), "[]");
190+
}
191+
192+
#[test]
193+
fn test_quote_idents() {
194+
assert_eq!(quote!(32).to_string(), "32");
195+
assert_eq!(quote!(struct).to_string(), "struct");
196+
}
197+
198+
#[test]
199+
fn test_quote_hash_simple_literal() {
200+
let a = 20;
201+
assert_eq!(quote!(#a).to_string(), "20");
202+
let s: String = "hello".into();
203+
assert_eq!(quote!(#s).to_string(), "hello");
204+
}
205+
206+
fn mk_ident(name: &str) -> tt::Ident {
207+
tt::Ident { text: name.into(), id: tt::TokenId::unspecified() }
208+
}
209+
210+
#[test]
211+
fn test_quote_hash_token_tree() {
212+
let a = mk_ident("hello");
213+
214+
let quoted = quote!(#a);
215+
assert_eq!(quoted.to_string(), "hello");
216+
let t = format!("{:?}", quoted);
217+
assert_eq!(t, "Subtree { delimiter: None, token_trees: [Leaf(Ident(Ident { text: \"hello\", id: TokenId(4294967295) }))] }");
218+
}
219+
220+
#[test]
221+
fn test_quote_simple_derive_copy() {
222+
let name = mk_ident("Foo");
223+
224+
let quoted = quote! {
225+
impl Clone for #name {
226+
fn clone(&self) -> Self {
227+
Self {}
228+
}
229+
}
230+
};
231+
232+
assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {}}}");
233+
}
234+
235+
#[test]
236+
fn test_quote_derive_copy_hack() {
237+
// Assume the given struct is:
238+
// struct Foo {
239+
// name: String,
240+
// id: u32,
241+
// }
242+
let struct_name = mk_ident("Foo");
243+
let fields = [mk_ident("name"), mk_ident("id")];
244+
let fields = fields
245+
.into_iter()
246+
.map(|it| quote!(#it: self.#it.clone(), ).token_trees.clone())
247+
.flatten();
248+
249+
let list = tt::Subtree { delimiter: tt::Delimiter::Brace, token_trees: fields.collect() };
250+
251+
let quoted = quote! {
252+
impl Clone for #struct_name {
253+
fn clone(&self) -> Self {
254+
Self #list
255+
}
256+
}
257+
};
258+
259+
assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {name : self . name . clone () , id : self . id . clone () ,}}}");
260+
}
261+
}

0 commit comments

Comments
 (0)