synでの構文解析がどのようになっているかを調べるために、以下のようなコードを書く。
parse_macro_inputで作成された構文木myinputをトレースするために、syn::visit::Visitを使用。ノードを再帰的に処理する。処理内容は「ノードを文字列化してコンパイル時に表示する」というもの。
extern crate proc_macro; use std::{string, str::FromStr}; use proc_macro::TokenStream; use quote::ToTokens; // to_token_stream() を使うために必要 use syn::{ parse_macro_input, Expr, // Cargo.toml 内 syn = {"2.0", features = ["full","visit"] } visit // "visit" が必要 }; use syn::visit::Visit; // vis.visit_expr() を使うために必要
// 構造体を定義 struct MyExprVisitor { indent: usize, // printでインデントしたいのでこのメンバを用意 }
// MyExprVisitor にvisit_expr()を実装 // C++でいうと、「class MyExprVisitor : public syn::visit::Visit」に近い impl<'a> syn::visit::Visit<'a> for MyExprVisitor { fn visit_expr(&mut self, node: &'a Expr) { // インデント for _ in 0..self.indent { print!(" "); } // ノードを文字列化 let astext = node.to_token_stream().to_string(); println!("{}", astext); self.indent += 1; // 再帰的に処理 syn::visit::visit_expr(self, node); self.indent -= 1; } }
// 式のトークンストリームを処理するマクロ #[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!("** 式の構文木ができているか確認 *********************"); let mut myvis = MyExprVisitor{indent:0}; myvis.visit_expr(&myinput); input }
extern crate mymacro; fn main() { let value:i32; let seven:i32 = 7;
// 式に対してマクロを適用 mymacro::tokenstream_analyze_for_formula! { value = (seven + 5) * 2 }
print!("結果 {}",value); }
Compiling mymain v0.1.0 (D:\mydev\Rust\mymacro-syn\mymain) ** 式の構文木ができているか確認 ********************* value = (seven + 5) * 2 value (seven + 5) * 2 (seven + 5) seven + 5 seven 5 2 Finished dev [unoptimized + debuginfo] target(s) in 0.49s
ただ文字列に変換しただけでは扱い方がよくわからないので、nodeをそれが何で(ここではExpr::Binary)、どのようなものが入っているのか(op,left,right)を取り出す。
// MyExprVisitor にvisit_expr()を実装 // C++でいうと、「class MyExprVisitor : public syn::visit::Visit」に近い impl<'a> syn::visit::Visit<'a> for MyExprVisitor { fn visit_expr(&mut self, node: &'a Expr) { let mut indentstr:String = "".to_string(); // インデント for _ in 0..self.indent { indentstr.push_str(" "); }; println!("{}***************",indentstr); match node { // Expr::Binaryはシンプルな加減乗除のような式 // matchでnodeがExpr::Binaryであるかどうかを判定し、もしそうならその内容をmyitemへ代入 Expr::Binary(myitem) => { // if letでmyitemがExprBinaryであるかどうかを判定し、もしそうならop,left,rightを取り出す if let ExprBinary{op:myop, left:myleft, right:myright, ..} = myitem{ println!("{} @ {}",indentstr, myop.to_token_stream()); println!("{} @ {}",indentstr, myleft.to_token_stream()); println!("{} @ {}",indentstr, myright.to_token_stream()); } }, _=>{} } println!("{}***************",indentstr); self.indent += 1; // 再帰的に処理 syn::visit::visit_expr(self, node); self.indent -= 1; } }
Compiling mymain v0.1.0 (D:\mydev\Rust\mymacro-syn\mymain) ** 式の構文木ができているか確認 ********************* *************** *************** *************** *************** *************** @ * @ (seven + 5) @ 2 *************** *************** *************** *************** @ + @ seven @ 5 *************** *************** *************** *************** *************** *************** *************** Finished dev [unoptimized + debuginfo] target(s) in 0.32s