aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorEdwin Cheng <[email protected]>2019-11-11 06:14:39 +0000
committerEdwin Cheng <[email protected]>2019-11-11 06:14:39 +0000
commit1637a8a59071d8c7976ffc8d04edc5b7f54ae40b (patch)
tree8f1cf805126f644c5c5104076a0337366aa4a559 /crates
parentc46768d13dd34bbe878cc62eca4af873ffbb7c22 (diff)
Add quote macro
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_hir_expand/src/lib.rs1
-rw-r--r--crates/ra_hir_expand/src/quote.rs261
2 files changed, 262 insertions, 0 deletions
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 6b71738ee..21d666f13 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -11,6 +11,7 @@ pub mod name;
11pub mod hygiene; 11pub mod hygiene;
12pub mod diagnostics; 12pub mod diagnostics;
13pub mod builtin_macro; 13pub mod builtin_macro;
14pub mod quote;
14 15
15use std::hash::{Hash, Hasher}; 16use std::hash::{Hash, Hasher};
16use std::sync::Arc; 17use std::sync::Arc;
diff --git a/crates/ra_hir_expand/src/quote.rs b/crates/ra_hir_expand/src/quote.rs
new file mode 100644
index 000000000..9cd17f0e3
--- /dev/null
+++ b/crates/ra_hir_expand/src/quote.rs
@@ -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]
10macro_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]
104macro_rules! quote {
105 ( $($tt:tt)* ) => {
106 $crate::quote::IntoTt::to_subtree($crate::__quote!($($tt)*))
107 }
108}
109
110pub(crate) trait IntoTt {
111 fn to_subtree(self) -> tt::Subtree;
112 fn to_tokens(self) -> Vec<tt::TokenTree>;
113}
114
115impl 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
125impl 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
135pub(crate) trait ToTokenTree {
136 fn to_token(self) -> tt::TokenTree;
137}
138
139impl ToTokenTree for tt::TokenTree {
140 fn to_token(self) -> tt::TokenTree {
141 self
142 }
143}
144
145impl ToTokenTree for tt::Subtree {
146 fn to_token(self) -> tt::TokenTree {
147 self.into()
148 }
149}
150
151macro_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
171impl_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)]
184mod 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}