Add quote macro

This commit is contained in:
Edwin Cheng 2019-11-11 14:14:39 +08:00
parent c46768d13d
commit 1637a8a590
2 changed files with 262 additions and 0 deletions

View file

@ -11,6 +11,7 @@ pub mod name;
pub mod hygiene;
pub mod diagnostics;
pub mod builtin_macro;
pub mod quote;
use std::hash::{Hash, Hasher};
use std::sync::Arc;

View file

@ -0,0 +1,261 @@
//! 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
}
};
// 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 '.')};
( $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()}};
&str => self { tt::Literal{text: self.to_string().into()}};
String => self { tt::Literal{text: self.into()}};
tt::Leaf => self { self };
tt::Literal => self { self };
tt::Ident => self { self };
tt::Punct => self { self }
}
#[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 { 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
.into_iter()
.map(|it| quote!(#it: self.#it.clone(), ).token_trees.clone())
.flatten();
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 () ,}}}");
}
}