RustのマクロだけでBASICっぽい何かを作った

# 書くまでの流れ
ふと`Tiny BASIC`を書きたいと思った
ちょうど達人プログラマーを読んで、ミニ言語を作れ、というTipがあったからかもしれない
[Rebuild.fm](https://rebuild.fm/169/)ではそんなのやめとけ！と言われていたような気もするけれど、ちょっとやってみるか！
↓
どうやらパーサコンビネータ([Geal/nom](https://github.com/Geal/nom)や[Marwes/combine](https://github.com/Marwes/combine))を使うといい感じらしい
↓
全然知識は無いが、いい勉強になりそう
`nom`のサンプルを動かしたり、Qiitaの記事([Rustのパーサコンビネータnomを使ってみよう](http://qiita.com/k5n/items/e95842744fc5db931d03))を真似する
↓
`nom`で使う大量のマクロに面食らう
これ、もうミニ言語、もとい、DSLじゃねーか！
↓
上記の記事を書いている人が、[Rustのマクロを覚える](http://qiita.com/k5n/items/758111b12740600cc58f)も書いていた
なので、マクロに体を慣らそうと考える
↓
Rustのマクロ、薄々気づいてはいたけどめっちゃ強力だな (Lispの影響なのだろうか？)
`BASIC` っぽいなにかマクロでいけるんじゃないか？？？

# できたもの
BASICっぽいものができました
以下のものが実行できます

```rust
#[allow(non_snake_case)]
#[allow(unused_assignments)]
fn main()
{
    interpret_basic!{
        PRINT "Hello, world!\n";

        LET A = 1 + 1;
        LET B = A + 2;
        PRINT "A = ", A, ", ";
        PRINT "B = ", B;
        PRINT ;

        LET A = 2;
        IF (A == 2) {
            PRINT "The condition is matched !\n";
            PRINT "A is 2\n";
        }

        FOR A = 1 TO 10 {
            PRINT A, ", ";
        }
        PRINT;

        LET_MUT N = 0;
        PRINT "N = ";
        INPUT N;

        PRINT "Your input number is ", N, "\n";

        FN FIZZ_BUZZ(N) {
            PRINT "It is ";

            IF ((N % 15) == 0) {
                PRINT "FizzBuzz\n";
            } ELSE {
                IF ((N % 3) == 0) {
                    PRINT "Fizz\n";
                } ELSE {
                    IF ((N % 5) == 0) {
                        PRINT "Buzz\n";
                    } ELSE {
                        PRINT N, "\n";
                    }
                }
            }
        }

        FIZZ_BUZZ(N);

        FN FIBONACCI(N) {
            IF ((N == 0) || (N == 1)) {
                RETURN N;
            }

            LET_MUT X = 0;
            LET_MUT Y = 1;
            LET_MUT F = 0;
            FOR _ = 2 TO (N + 1) {
                F = X + Y;
                X = Y;
                Y = F;
            }

            RETURN F;
        }

        LET F = FIBONACCI(N);
        PRINT N, "th fibonacci number is ", F, "\n";

        PRINT "FIN\n";
    };
}
```
BASICは変種がたくさんあるようなので、頑張ればBASICって言い張ってもいいかなーと思います
次のものを実装しました

* 変数(整数型のみ)
* 外部入力(整数のみ)
* `IF`, `IF ELSE`命令による条件分岐
* `FOR`命令によるループ

main関数の中に、FizzBuzzやフィボナッチ数の計算など、サンプルが雑多に書いてあります
関数はRustのクロージャによる実装のため、再帰呼出しできません。
サンプルのフィボナッチがループで実装されているのはそういう理由です。

## 実装
[Github - mopp/CodeGarage/rust/macro_bs.rs](https://github.com/mopp/CodeGarage/blob/master/rust/macro_bs.rs)

```rust
macro_rules! interpret_basic {
    () => {
        ()
    };

    (PRINT ; $( $rest:tt )*) => {
        println!("");
        interpret_basic!($( $rest )*);
    };

    (PRINT $( $args:expr ),+ ; $( $rest:tt )*) => {
        $( print!("{}", $args); )+
        interpret_basic!($( $rest )*);
    };

    (INPUT $var:ident ; $( $rest:tt )*) => {
        $var = {
            // https://github.com/rust-lang/rust/issues/23818
            use std::io::Write;
            std::io::stdout().flush().expect("could not flush the stdout.");

            let mut number_string = String::new();
            std::io::stdin()
                .read_line(&mut number_string)
                .expect("Failed to read line");

            number_string
                .trim()
                .parse::<usize>()
                .expect("The input allows only a number.")
        };
        interpret_basic!($( $rest )*);
    };

    (LET_MUT $var:ident = $val:expr ; $( $rest:tt )*) => {
        let mut $var: usize = $val;
        interpret_basic!($( $rest )*);
    };

    (LET $var:ident = $val:expr ; $( $rest:tt )*) => {
        let $var: usize = $val;
        interpret_basic!($( $rest )*);
    };

    (IF ( $cond:expr ) { $( $inner_if:tt )* } ELSE { $( $inner_else:tt )* } $( $rest:tt )*) => {
        if $cond {
            interpret_basic!($( $inner_if )*);
        } else {
            interpret_basic!($( $inner_else )*);
        }
        interpret_basic!($( $rest )*);
    };

    (IF ( $cond:expr ) { $( $inner:tt )* } $( $rest:tt )*) => {
        if $cond {
            interpret_basic!($( $inner )*);
        }
        interpret_basic!($( $rest )*);
    };

    (FOR $var:pat = $begin:tt TO $end:tt { $( $inner:tt )* } $( $rest:tt )*) => {
        for $var in $begin..$end {
            interpret_basic!($( $inner )*);
        }
        interpret_basic!($( $rest )*);
    };

    ($var:ident = $val:expr ; $( $rest:tt )*) => {
        $var = $val;
        interpret_basic!($( $rest )*);
    };

    (FN $name:ident ( $( $args:ident ),* ) { $( $inner:tt )* } $( $rest:tt )*) => {
        let $name = |$( $args: usize ),*| {
            interpret_basic!($( $inner )*);
        };
        interpret_basic!($( $rest )*);
    };

    (RETURN $value:expr; $( $rest:tt )*) => {
        return $value;
    };

    ($func_name:ident ($( $args:ident ),*) ; $( $rest:tt )*) => {
        $func_name($( $args ),*);
        interpret_basic!($( $rest )*);
    }
}
```

# 解説
コードを貼り付けるだけでは面白くないので、簡単な解説を書いてみます
マクロ自体については上記した、[Rustのマクロを覚える](http://qiita.com/k5n/items/758111b12740600cc58f)と[公式ドキュメント](https://doc.rust-lang.org/1.1.0/book/macros.html)を参考に書きました

## 命令はセミコロン区切り
まず大きな設定として、セミコロン区切り、ということにしました
そのための基本となるコードは以下のようになります

```rust
macro_rules! interpret_basic {
    () => {
        ()
    };

    (PRINT ; $( $rest:tt )*) => {
        println!("");
        interpret_basic!($( $rest )*);
    };
}
```

一番はじめに実装したPRINT命令を見てみます
matcherが`(PRINT ; $( $rest:tt )*)`となっており、
引数無しの`PRINT`命令ですね

セミコロンの次にあるのがsingle token tree, `tt`の**0個以上の繰り返し**です
マクロをいまいちわかっていなかったので、繰り返しの解釈に少し手こずりました
この場合であれば0個以上のsingle token treeを受け取る、となります
というか、読みづらい

そして、`rest`という名前の通り、`PRINT`命令が終わったあとの、全ての残りをここで受け取ります
`interpret_basic!($( $rest )*);`のように再帰的にマクロを展開させます
ここで、`interpret_basic!`の引数が`$( $rest )*`となっていますが、これも繰り返しです
繰り返し展開された`$rest`が引数として渡されます

全ての命令を展開し終えたとき、`$rest`は空になります
なので、マクロの先頭に

```rust
() => {
    ()
};
```
という空の展開先を書いています

`RETURN`命令以外は全て、このように展開していきます
毎回同じことを書いていて、嫌なので、うまいこと直したかった…

先にセミコロン区切りで、ばらしてから書く命令のマッチングをしようと考えたのですが
一意に展開できない、曖昧だ、と、コンパイラにお叱りを受けてだめでした
`PRINT`命令が2通りあるのも同じエラーのためです

## `INPUT` 命令
これはそのままなので、解説しなくてもいいかと思います
`stdout`をフラッシュしてやらないと直前に改行が出力されていなかった場合に挙動がおかしくなるので、フラッシュしています
マクロなど関係なしにRustの問題？みたいです

## `LET`, `LET_MUT` 命令
これもそのままですね
全部`mut`にするとコンパイラがうるさいので、mutableかimmutableで分けました

## `IF`, `IF ELSE`命令
```rust
(IF ( $cond:expr ) { $( $inner_if:tt )* } ELSE { $( $inner_else:tt )* } $( $rest:tt )*) => { 
    if $cond {                                                                               
        interpret_basic!($( $inner_if )*);                                                   
    } else {                                                                                 
        interpret_basic!($( $inner_else )*);                                                 
    }                                                                                        
    interpret_basic!($( $rest )*);                                                           
};                                                                                           
                                                                                             
(IF ( $cond:expr ) { $( $inner:tt )* } $( $rest:tt )*) => {                                  
    if $cond {                                                                               
        interpret_basic!($( $inner )*);                                                      
    }                                                                                        
    interpret_basic!($( $rest )*);                                                           
};                                                                                           
```
書いているときは結構苦労したんですが、見返してみるとなんてことないですね
`()`と`{}`で囲まれている部分を引っこ抜いて、Rustの普通の構文になおしています
そのあと、それぞれを更に再帰展開します
matcherは上からマッチしていくので、`IF ELSE`を先に書くことに注意です



## `FOR` 命令
```rust
(FOR $var:pat = $begin:tt TO $end:tt { $( $inner:tt )* } $( $rest:tt )*) => { 
    for $var in $begin..$end {                                                
        interpret_basic!($( $inner )*);                                       
    }                                                                         
    interpret_basic!($( $rest )*);                                            
};                                                             
```
`FOR`命令も`IF`命令と似たようにRustのforに展開して、ブロック内を再帰展開しているだけになります

## `FN`, `RETURN` 命令
```rust
(FN $name:ident ( $( $args:ident ),* ) { $( $inner:tt )* } $( $rest:tt )*) => { 
    let $name = |$( $args: usize ),*| {                                         
        interpret_basic!($( $inner )*);                                         
    };                                                                          
    interpret_basic!($( $rest )*);                                              
};                                                                              
                                                                                
(RETURN $value:expr; $( $rest:tt )*) => {                                       
    return $value;                                                              
}; 
```
関数の定義と戻り値を返す部分です
最初はRustの普通の関数に対応付けようと思ったのですが、戻り値型の部分をマクロで展開するのがめんどうだなと思い、クロージャを使いました。
(と、書いていて思ったけど、常に何かしらの整数を返すようにすればよかったのでは…)
引数部分をカンマ区切りの0個以上の繰り返しで表現しているので引数の数は好きにできます

`RETURN`命令はそのままreturnへの対応付けにしました。
なので、Rustのifの中でもいい感じに動いてくれています。

## 変数代入/関数呼出し
```rust
($var:ident = $val:expr ; $( $rest:tt )*) => { 
    $var = $val;                               
    interpret_basic!($( $rest )*);             
};                                             

($func_name:ident ($( $args:ident ),*) ; $( $rest:tt )*) => { 
    $func_name($( $args ),*);                                 
    interpret_basic!($( $rest )*);                            
}                                                             
```
マクロで再帰展開していく都合上書いたもので、特に面白みはないですね

# 感想
BASIC的なのを作ってみたいな欲が多少満たされた
また、90行くらいのマクロでこんなに見た目が変わるんだなと感じた
しかし、便利ではあるが、可読性やとっつきやすさがだいぶ違うのでtraitなどでできることはそっちでやるべきだと思います

振り返ってみると、Rustの構文に対応付けているだけなので、そんなに面白いものでもなかったかな、と感じてしまい、少しだけ残念
だが、マクロがよくわかっていなかったので、とても良い勉強になった