From b7aa4898e0841ab8199643f89a0caa967b698ca8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Aug 2020 16:26:29 +0200 Subject: Rename ra_hir_expand -> hir_expand --- crates/hir_expand/src/quote.rs | 282 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 crates/hir_expand/src/quote.rs (limited to 'crates/hir_expand/src/quote.rs') diff --git a/crates/hir_expand/src/quote.rs b/crates/hir_expand/src/quote.rs new file mode 100644 index 000000000..219bc2097 --- /dev/null +++ b/crates/hir_expand/src/quote.rs @@ -0,0 +1,282 @@ +//! 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::::new() + }; + + ( @SUBTREE $delim:ident $($tt:tt)* ) => { + { + let children = $crate::__quote!($($tt)*); + tt::Subtree { + delimiter: Some(tt::Delimiter { + kind: tt::DelimiterKind::$delim, + id: tt::TokenId::unspecified(), + }), + token_trees: $crate::quote::IntoTt::to_tokens(children), + } + } + }; + + ( @PUNCT $first:literal ) => { + { + vec![ + tt::Leaf::Punct(tt::Punct { + char: $first, + spacing: tt::Spacing::Alone, + id: tt::TokenId::unspecified(), + }).into() + ] + } + }; + + ( @PUNCT $first:literal, $sec:literal ) => { + { + vec![ + tt::Leaf::Punct(tt::Punct { + char: $first, + spacing: tt::Spacing::Joint, + id: tt::TokenId::unspecified(), + }).into(), + tt::Leaf::Punct(tt::Punct { + char: $sec, + spacing: tt::Spacing::Alone, + id: tt::TokenId::unspecified(), + }).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::>(); + let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*)); + tokens.append(&mut tail_tokens); + tokens + } + }; + + // 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 ';')}; + ( :: ) => {$crate::__quote!(@PUNCT ':', ':')}; + ( . ) => {$crate::__quote!(@PUNCT '.')}; + ( < ) => {$crate::__quote!(@PUNCT '<')}; + ( > ) => {$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; +} + +impl IntoTt for Vec { + fn to_subtree(self) -> tt::Subtree { + tt::Subtree { delimiter: None, token_trees: self } + } + + fn to_tokens(self) -> Vec { + self + } +} + +impl IntoTt for tt::Subtree { + fn to_subtree(self) -> tt::Subtree { + self + } + + fn to_tokens(self) -> Vec { + 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(), id: tt::TokenId::unspecified()} }; + usize => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()}}; + i32 => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()}}; + tt::Leaf => self { self }; + tt::Literal => self { self }; + tt::Ident => self { self }; + tt::Punct => self { self }; + &str => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into(), id: tt::TokenId::unspecified()}}; + String => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into(), id: tt::TokenId::unspecified()}} +} + +#[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(); + assert_eq!(quote!(#s).to_string(), "\"hello\""); + } + + 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 $\n IDENT hello 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).flatten(); + + let list = tt::Subtree { + delimiter: Some(tt::Delimiter { + kind: tt::DelimiterKind::Brace, + id: tt::TokenId::unspecified(), + }), + 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 () ,}}}"); + } +} -- cgit v1.2.3