スポンサーリンク

Rustの手続き的マクロを考える(3)synで構文解析(2)

synでの構文解析がどのようになっているかを調べるために、以下のようなコードを書く。

parse_macro_inputで作成された構文木myinputをトレースするために、syn::visit::Visitを使用。ノードを再帰的に処理する。処理内容は「ノードを文字列化してコンパイル時に表示する」というもの。

lib.rs

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
}

main.rs

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でmyitemExprBinaryであるかどうかを判定し、もしそうなら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

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: