手続き型マクロは、与えられたトークンストリームを編集することでコードをコンパイル時に書き換えるのだが、この作業を楽にするためにsynクレートというものがあり、synを使うと構文木の操作がかなり楽にできる(らしい)。
syn::parse_macro_input!で構文解析を行い、構文木(=AST=Abstract Syntax Tree)を作成する。
この時、トークンストリームを、ItemFnやExprなどに変換して渡す必要がある。マクロに渡された内容が関数であればItemFn、式であればExprに変換する。
extern crate proc_macro; use proc_macro::TokenStream; use syn::{ parse_macro_input, Expr, // Cargo.toml 内 syn = {"2.0", features = ["full"] } ItemFn, // "full" が必要 };
// 関数を処理する場合 as ItemFn を使用 #[proc_macro] pub fn tokenstream_analyze_for_function(input: TokenStream) -> TokenStream { let inputcopy = input.clone(); let myinput:ItemFn = parse_macro_input!(inputcopy as ItemFn);// トークンストリームを解析 return input; }
// 式を処理する場合 as Expr を使用 #[proc_macro] pub fn tokenstream_analyze_for_formula(input: TokenStream) -> TokenStream { let inputcopy = input.clone(); let myinput:Expr = parse_macro_input!(inputcopy as Expr);// トークンストリームを解析 return input; }
synを使用するためにはCargo.tomlに以下の記述が必要。ItemFnを使用するためには、features=["full"]を加える必要がある。
このマクロは、以下のようにして使用する。(注意 結果は何も変わらない)
extern crate mymacro;
// 関数に対してマクロを適用 mymacro::tokenstream_analyze_for_function!{ fn myfunction(i:i32)->i32{ i + 1 } }
fn main() { let value:i32; let seven:i32 = 7;
// 式に対してマクロを適用 mymacro::tokenstream_analyze_for_formula! { value = (seven + 5) * 2 }
print!("結果 {}",value); }
上記のマクロは何も起こらないので、syn::ItemFnやsyn::Expr型の変数myinputにちゃんと値が入っているのか確認したい。そのためにquoteクレートを使用して構文木を表示してみる。
extern crate proc_macro; use std::{string, str::FromStr}; use proc_macro::TokenStream; use syn::{ parse_macro_input, Expr, ItemFn // "full" が必要 }; use quote::ToTokens;
// 関数 #[proc_macro] pub fn tokenstream_analyze_for_function(input: TokenStream) -> TokenStream { // トークンストリームを解析 let inputcopy = input.clone(); let myinput:ItemFn = parse_macro_input!(inputcopy as ItemFn); println!("** 関数の構文木ができているか確認 *********************"); // quoteを使って文字列化 let astext = myinput.to_token_stream().to_string(); println!("{}", astext); input }
// 式 #[proc_macro] pub fn tokenstream_analyze_for_formula(input: TokenStream) -> TokenStream { // トークンストリームを解析 let inputcopy = input.clone(); let myinput:Expr = parse_macro_input!(inputcopy as Expr); println!("** 式の構文木ができているか確認 *********************"); // quoteを使って文字列化 let astext = myinput.to_token_stream().to_string(); println!("{}", astext); input }
これをビルドすると、ビルド中に以下が表示される。