RustのProcedural Macroを使って正規表現未対応の（place holderには対応してる）routerを作る

# Procedural Macroとは
引数と戻り値がTokenStreamである関数です。  
macro_rules!で宣言するマクロと違って、ふつうの関数なのでHTTPリクエストやファイルの読み書き削除のようにふつうの関数ができることを行うこともできます。

さて、多くのWebフレームワークは実行時にRouterを評価しているようですがHeapにアロケーションしたりするアルゴリズムよりも、match statementを使ったほうが早いに違いありません。

というわけで、この記事ではProcedural Macroを使って、Match statementで動くRouterを作ってみます。  

と、本題に入る前に...
この記事書いたの2020年3月なんですよねぇ....
その当時と今で少し変化したことがあるのですが、それは最後のほうに書きます。

# マクロの目標
このruby on railsに出てきそうなrouterを

```rust
magic_macro::router! {
    scope "/:hello/world" {
        // pathがマッチした場合の戻り値
        return 100;
    }
    scope "/api" {
        scope "/v1/:name" {
           return 1;
        }
        scope "/v2" {
           return 2;
        }
    }
}
```

これに変換する。

```rust
let item = match segments[..] {
    [_hello, "world" ] => 100,
    ["api", "v1", _name] => 1,
    ["api", "v2"] => 2,
    _ => return None 
};
return Some(item)
```

assert_eqだとこう。

```rust
assert_eq!(magic_macro_router("/konnichiwa/world"), Some(100));
assert_eq!(magic_macro_router("/api/v1/me"), Some(1));
assert_eq!(magic_macro_router("/api/v2"), Some(2));
assert_eq!(magic_macro_router("path-that-does-not-exist"), None);
```

整理すると、
- `:`で始まるセグメントはパラメータ
- Regexpは使えない
- scopeがliteralを宣言するキーワードで、対応するグループ内にリターンする値を記載する
- scopeをネストするときは、親scopeのリテラルに子scopeのリテラルをconcatする。
- 合致が無かった場合は、`None`を返す。

# 実装

1. 概観

Scopeストラクトについては後述します。

```rust
#[proc_macro]
pub fn router(ts: TokenStream) -> TokenStream {
    let scopes: Vec<Scope> = {
        /* 
            3.で詳細
            TokenStreamをScopeストラクトに変換する。
            ネストされたデータを平たくして処理しやすくすることが目的  
        */
    };
    let expanded: Vec<(Vec<String>, Vec<TokenTree>)> = {
        /*
            4.で詳細 
            Scopeのうち、return_valueがあるものだけを取り出し
           (path, return_value)のペアにして返す 
         */
    };
    
    let match_stmt_func: fn (&str) -> String = {
        /*  
            5. で詳細          
            この関数のbody部分をstringとして返すclosureを返す。
        */
    };
    
    // 先ほど返したclosureにsliceを作成する関数を実行するコードがあるstringを渡して完成
    let code = format!{
        r#"
            fn magic_macro_router(path: &str) -> Option<i32> {{
                {}
            }}
        "#,  match_stmt_func(r#"path.split("/").collect::<Vec<&str>>()[..]"#)
    };
    
    // これでTokenStreamになる
    // エラーがあれば、コンパイラがはじく
    code.parse().unwrap()
}
```

2. scopeのストラクト

Scopeを表すデータタイプです。Tupleでも実装可能ですが、見づらかったのでデータタイプを追加しました。

```rust
struct Scope {
    // ネストできるので、Vecを使って親の情報を継承していく
    path: Vec<String>,
    // 戻り値
    return_value: Option<Vec<TokenTree>>,
    // ネストされたスコープ
    nested_scopes: Vec<(String, TokenStream)>
}
```

3. let scopes: Vec<Scope>

TokenStreamをScopeストラクトに変換する。
ネストされたデータを平たくして処理しやすくすることが目的  

```rust
let scopes = {
        // 後述
        let mut counter = 0;

        // 後述のループのロジックの特性のため、
        // 引数として受け取ったTokenStreamを持つScopeを作成する
        let root_scope = Scope {
            path: vec![],
            return_value: None,
            nested_scopes: vec![("".to_owned(), ts.clone())]
        };
        let mut scopes_found = vec![root_scope];

        // groupをパースするロジック
        // 後述のloopにいれると読みづらくなるので分けた
        let parser_logic = |path, stream: TokenStream| {
            let mut nested_scopes = vec![];
            let mut return_value = None;
            let mut iter = stream.into_iter();
            while let Some(tt) = iter.next() {
                let ident = if let TokenTree::Ident(ident) = tt {
                    ident
                } else {
                    continue
                };

                match ident.to_string().as_str() {
                    // parse scope
                    "scope" => {
                        if let TokenTree::Literal(l) = iter.next().expect("unexpected syntax") {
                            if let TokenTree::Group(g) = iter.next().expect("unexpected syntax") {
                                let s = l.to_string().replacen("\"", "", 2);
                                nested_scopes.push((s, g.stream()));
                            }
                        };
                    }
                    // parse return value
                    "return" => {
                        let mut temp = vec![];
                        // collect until it reaches a delimiter
                        while let Some(i) = iter.next() {
                            match i {
                                TokenTree::Punct(p) if p.as_char() == ';' => {
                                    break;
                                }
                                _ => temp.push(i)
                            }
                        }
                        return_value = Some(temp);
                    }
                    _ => ()
                }
            };
            Scope {
                path,
                return_value,
                nested_scopes
            }
        };

        loop {
            // counterはloopの最後に１増やす。
            // また、scopes_foundには見つかったスコープをpushしていく
            // Noneがでたらもう対応すべきスコープはないということなのでbreakする
            let scope = match scopes_found.get(counter) {
                None => break,
                Some(s) => s
            };

            let mut cloned_path = scope.path.clone();
            // ネストされたスコープの内部を評価する
            // ネストされたスコープがなければ、scopes_foundにデータは追加されない。
            for (nest_literal, scoped_group) in scope.nested_scopes.clone().drain(..) {

                cloned_path.push(nest_literal);
                // scopeに対応するgroupをパースして、ネストされたスコープやreturn valueを探す
                let new_scope = parser_logic(cloned_path.clone(), scoped_group);
                // 見つかったscopeはpushする
                scopes_found.push(new_scope);
                // 今追加したliteralを取り除く
                cloned_path.pop();
            }

            counter += 1;
        }

        scopes_found
    };
```

4. let expanded: Vec<(Vec<String>, Vec<TokenTree>)>

Scopeのうち、return_valueがあるものだけを取り出し
(path, return_value)のペアにして返す 

```rust
let expanded = {
    let mut scopes = scopes;
    // return_valueががなければmatch-statementに含める必要が無いので除外する
    let filtered = scopes
        .drain(..)
        .filter(|e| e.return_value.is_some());

    // return_valueとpathのペアを作成する
    let mut vec = vec![];
    for s in filtered {
        vec.push((s.path, s.return_value.unwrap()));
    }

    vec
};
```

5. let match_stmt_func: fn (&str) -> String

この関数のbody部分をstringとして返すclosureを返す。 

```rust
let match_stmt_func: fn (&str) -> String = {
    let mut match_stmt_kv = String::new();
    for (path, rv) in expanded {
        let value = {
            let mut vec = vec![];
            // スラッシュがあるであろうpathをjoinしたあと、
            // セグメントに分けて、全てのセグメントをmatch_statementで使われる際のコードに変換してる
            path.join("").split("/").for_each(|seg| {
                let s = if seg.find(":").is_none() {
                    format!("{:?}", seg)
                } else {
                    "_".to_owned()
                };
                vec.push(s)
            });
            format!("[ {} ]", vec.join(", "))
        };

        let returning_value = {
            let mut returning_value = String::new();

            // stringに変換する
            rv.iter().for_each(|tt| {
                returning_value.push_str(tt.to_string().as_str());
            });
            returning_value
        };

        // match statementのbracket内部を作っていく
        let s = format!("{} => {{ return Some({}); }}", value, returning_value);
        match_stmt_kv.push_str(s.as_str());
    }

    // matchのkeyword直後の変数を指定することで、match statementを作成できるclosureを返す
    move |s: &str| {
        format!(r#"
            match {} {{
                {}
                _ => return None
            }}
        "#,
                s,
                match_stmt_kv
        )
    }
};
```

# まとめ
マクロはコードを作るコードなのですが、コードを作るコードな感じを出たほうが、
経験のない方もとっつきやすいと思ったので、あえてTokenTreeとかを文字列に変換しました。

これ作ったのrustのprocedural macroを触りだした頃なので、ちょっといろいろひどいですね。
これをもう少しジェネリックにできるので、近いうちにまた投稿しようと思います。

# Proc Macroの変化について
- 衛生問題が解決したこと

もともと、hygeineの問題が解決出来ていないという理由で関数内でproc macroを使うことが出来なかったのですが、現在はできます。