rust/crates/ra_hir_expand/src/quote.rs

270 lines
7.6 KiB
Rust
Raw Normal View History

2019-11-11 00:14:39 -06:00
//! A simplified version of quote-crate like quasi quote macro
// A helper macro quote macro
// FIXME:
// 1. Not all puncts are handled
// 2. #()* pattern repetition not supported now
// * But we can do it manually, see `test_quote_derive_copy_hack`
#[doc(hidden)]
#[macro_export]
macro_rules! __quote {
() => {
Vec::<tt::TokenTree>::new()
};
( @SUBTREE $delim:ident $($tt:tt)* ) => {
{
let children = $crate::__quote!($($tt)*);
let subtree = tt::Subtree {
delimiter: tt::Delimiter::$delim,
token_trees: $crate::quote::IntoTt::to_tokens(children),
};
subtree
}
};
( @PUNCT $first:literal ) => {
{
vec![
tt::Leaf::Punct(tt::Punct {
char: $first,
spacing: tt::Spacing::Alone,
}).into()
]
}
};
( @PUNCT $first:literal, $sec:literal ) => {
{
vec![
tt::Leaf::Punct(tt::Punct {
char: $first,
spacing: tt::Spacing::Joint,
}).into(),
tt::Leaf::Punct(tt::Punct {
char: $sec,
spacing: tt::Spacing::Alone,
}).into()
]
}
};
// hash variable
( # $first:ident $($tail:tt)* ) => {
{
let token = $crate::quote::ToTokenTree::to_token($first);
let mut tokens = vec![token.into()];
let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
tokens.append(&mut tail_tokens);
tokens
}
};
( ## $first:ident $($tail:tt)* ) => {
{
let mut tokens = $first.into_iter().map($crate::quote::ToTokenTree::to_token).collect::<Vec<tt::TokenTree>>();
let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
tokens.append(&mut tail_tokens);
tokens
}
};
2019-11-11 00:14:39 -06:00
// Brace
( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) };
// Bracket
( [ $($tt:tt)* ] ) => { $crate::__quote!(@SUBTREE Bracket $($tt)*) };
// Parenthesis
( ( $($tt:tt)* ) ) => { $crate::__quote!(@SUBTREE Parenthesis $($tt)*) };
// Literal
( $tt:literal ) => { vec![$crate::quote::ToTokenTree::to_token($tt).into()] };
// Ident
( $tt:ident ) => {
vec![ {
tt::Leaf::Ident(tt::Ident {
text: stringify!($tt).into(),
id: tt::TokenId::unspecified(),
}).into()
}]
};
// Puncts
// FIXME: Not all puncts are handled
( -> ) => {$crate::__quote!(@PUNCT '-', '>')};
( & ) => {$crate::__quote!(@PUNCT '&')};
( , ) => {$crate::__quote!(@PUNCT ',')};
( : ) => {$crate::__quote!(@PUNCT ':')};
( :: ) => {$crate::__quote!(@PUNCT ':', ':')};
2019-11-11 00:14:39 -06:00
( . ) => {$crate::__quote!(@PUNCT '.')};
( $first:tt $($tail:tt)+ ) => {
{
let mut tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($first));
let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
tokens.append(&mut tail_tokens);
tokens
}
};
}
/// FIXME:
/// It probably should implement in proc-macro
#[macro_export]
macro_rules! quote {
( $($tt:tt)* ) => {
$crate::quote::IntoTt::to_subtree($crate::__quote!($($tt)*))
}
}
pub(crate) trait IntoTt {
fn to_subtree(self) -> tt::Subtree;
fn to_tokens(self) -> Vec<tt::TokenTree>;
}
impl IntoTt for Vec<tt::TokenTree> {
fn to_subtree(self) -> tt::Subtree {
tt::Subtree { delimiter: tt::Delimiter::None, token_trees: self }
}
fn to_tokens(self) -> Vec<tt::TokenTree> {
self
}
}
impl IntoTt for tt::Subtree {
fn to_subtree(self) -> tt::Subtree {
self
}
fn to_tokens(self) -> Vec<tt::TokenTree> {
vec![tt::TokenTree::Subtree(self)]
}
}
pub(crate) trait ToTokenTree {
fn to_token(self) -> tt::TokenTree;
}
impl ToTokenTree for tt::TokenTree {
fn to_token(self) -> tt::TokenTree {
self
}
}
impl ToTokenTree for tt::Subtree {
fn to_token(self) -> tt::TokenTree {
self.into()
}
}
macro_rules! impl_to_to_tokentrees {
($($ty:ty => $this:ident $im:block);*) => {
$(
impl ToTokenTree for $ty {
fn to_token($this) -> tt::TokenTree {
let leaf: tt::Leaf = $im.into();
leaf.into()
}
}
impl ToTokenTree for &$ty {
fn to_token($this) -> tt::TokenTree {
let leaf: tt::Leaf = $im.clone().into();
leaf.into()
}
}
)*
}
}
impl_to_to_tokentrees! {
u32 => self { tt::Literal{text: self.to_string().into()} };
usize => self { tt::Literal{text: self.to_string().into()}};
i32 => self { tt::Literal{text: self.to_string().into()}};
tt::Leaf => self { self };
tt::Literal => self { self };
tt::Ident => self { self };
2019-11-15 08:13:41 -06:00
tt::Punct => self { self };
&str => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into()}};
String => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into()}}
2019-11-11 00:14:39 -06:00
}
#[cfg(test)]
mod tests {
#[test]
fn test_quote_delimiters() {
assert_eq!(quote!({}).to_string(), "{}");
assert_eq!(quote!(()).to_string(), "()");
assert_eq!(quote!([]).to_string(), "[]");
}
#[test]
fn test_quote_idents() {
assert_eq!(quote!(32).to_string(), "32");
assert_eq!(quote!(struct).to_string(), "struct");
}
#[test]
fn test_quote_hash_simple_literal() {
let a = 20;
assert_eq!(quote!(#a).to_string(), "20");
let s: String = "hello".into();
2019-11-15 08:13:41 -06:00
assert_eq!(quote!(#s).to_string(), "\"hello\"");
2019-11-11 00:14:39 -06:00
}
fn mk_ident(name: &str) -> tt::Ident {
tt::Ident { text: name.into(), id: tt::TokenId::unspecified() }
}
#[test]
fn test_quote_hash_token_tree() {
let a = mk_ident("hello");
let quoted = quote!(#a);
assert_eq!(quoted.to_string(), "hello");
let t = format!("{:?}", quoted);
assert_eq!(t, "Subtree { delimiter: None, token_trees: [Leaf(Ident(Ident { text: \"hello\", id: TokenId(4294967295) }))] }");
}
#[test]
fn test_quote_simple_derive_copy() {
let name = mk_ident("Foo");
let quoted = quote! {
impl Clone for #name {
fn clone(&self) -> Self {
Self {}
}
}
};
assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {}}}");
}
#[test]
fn test_quote_derive_copy_hack() {
// Assume the given struct is:
// struct Foo {
// name: String,
// id: u32,
// }
let struct_name = mk_ident("Foo");
let fields = [mk_ident("name"), mk_ident("id")];
let fields =
fields.iter().map(|it| quote!(#it: self.#it.clone(), ).token_trees.clone()).flatten();
2019-11-11 00:14:39 -06:00
let list = tt::Subtree { delimiter: tt::Delimiter::Brace, token_trees: fields.collect() };
let quoted = quote! {
impl Clone for #struct_name {
fn clone(&self) -> Self {
Self #list
}
}
};
assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {name : self . name . clone () , id : self . id . clone () ,}}}");
}
}