[ポエム] パーサーを作ろうぜ☆（＾～＾）やっぱ　めんどくせ☆（＾ｑ＾）他の誰かが書いてくれたのを使うのがいいよな☆（＾ｑ＾）

```Plain
装備品
目：　ひのきの目
```

機械学習するとき　データを読み込みたくなる。
例えば　将棋なら棋譜を読み込みたくなる。
将棋の棋譜のファイルとか　.kif, .csa, バージョン違い、他、独自形式がいくつかある。

```Plain:daiwa.kif
# ----  Kifu for Windows V6.00 棋譜ファイル  ----
開始日時：2007/03/21
終了日時：2007/03/21
持ち時間：２時間
棋戦：大和証券杯特別対局
戦型：四間飛車
場所：品川プリンスホテル，クラブ eX
手合割：平手　　
先手：Bonanza
後手：渡辺明
手数----指手---------消費時間--
   1 ７六歩(77)   ( 0:01/00:00:01)
   2 ８四歩(83)   ( 2:32/00:02:32)
   3 ６六歩(67)   ( 0:01/00:00:02)
   4 ３四歩(33)   ( 0:11/00:02:43)
   5 ６八飛(28)   ( 0:01/00:00:03)
   6 ６二銀(71)   ( 0:14/00:02:57)
   7 ４八玉(59)   ( 0:01/00:00:04)
   8 ５四歩(53)   ( 0:17/00:03:14)
   9 ３八玉(48)   ( 0:01/00:00:05)
  10 ４二玉(51)   ( 0:20/00:03:34)
以下略
```

これの変換プログラムも　Blunder（ブランダー）のGitHubや、柿木（かきのき）将棋のホームぺージに置いてあるが、コンパイルしようとしたら　あれやこれや　揃えるのが
めんどくさいんで、それでは作ったものを　プログラマー以外の人に使ってもらうにも大変。　設定不要のパーサーを自分でイチから作る。


じゃあ、好きな欲望のレベルを選べだぜ。

* **バトル**的な理由から、将棋の指し手だけ欲しい
* **エンターテイメント**的な理由から、対局場などの情報も欲しい
* **ヒストリカル**的な理由から、ファイルを読み込んで、書きだしたら、ファイルが完全一致するような　可逆性　があるものが欲しい

など、理由が　いろいろ　あれば、方法も　それぞれある。ここでは　**バトル**　を選択する。

```Plain
装備品
目：　バトルの目
```

目を装備すると、今まで景色だったものは、素材に変わる。

```
   1 ７六歩(77)   ( 0:01/00:00:01)
   2 ８四歩(83)   ( 2:32/00:02:32)
   3 ６六歩(67)   ( 0:01/00:00:02)
   4 ３四歩(33)   ( 0:11/00:02:43)
```

上記のような　手数で始まってるとこだけ　欲しくて、

```
# ----  Kifu for Windows V6.00 棋譜ファイル  ----
開始日時：2007/03/21
終了日時：2007/03/21
持ち時間：２時間
```

上記のようなヘッダーは要らない☆
欲しいもの、捨てていいものがはっきりすると、そのあとの行動もスマートになる。

仕様書がどこに落ちてあるのか見つからなかったので、
実装を経験的に調べてみると

```
   1
   2
   3
   4
   5
   6
   7
   8
   9
  1
  2
  3
  4
  5
  6
  7
  8
  9
 1
 2
 3
 4
 5
 6
 7
 8
 9
1
2
3
4
5
6
7
8
9
```

上記の36パターンで始まる行は　将棋の指し手のようだ。

疑似コードで書くと

```:疑似コード
foreach line in file
{
    if line.starts_with("   1") | line.starts_with("   2") | line.starts_with("   3")
 | line.starts_with("   4") | | line.starts_with("   5") | line.starts_with("   6")
 | line.starts_with("   7")    以下略) 
    {
        # ここに処理を書く
    }
}
```

というコピペ技や、

```:疑似コード
foreach line in file
{
    line <= line.trim_begin()
    if try_parse_int(line[0])
    {
        # ここに処理を書く
    }
}
```

といった　数字の出現判定だけでいけそうだ。

Rust言語で文字列パーサーを書いているんだが、「始」が 3byte で　めんどくさい。１文字切り取るのがめんどくさい。

```
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside '開' (bytes 0..3) of `開始日時：2007/03/21`', libcore\str\mod.rs:2111:5
```

パーサー以前に　文字列が　めんどくさい。

```
    Finished dev [unoptimized + debuginfo] target(s) in 1.76s
     Running `target\debug\examples\eat_a_kif.exe --path C:/muzudho/kifuwarabe-wcsc29-learn/output-wcsc-record/copied-daiwa/daiwa.kif`
args.path = 'C:/muzudho/kifuwarabe-wcsc29-learn/output-wcsc-record/copied-daiwa/daiwa.kif'.
Ignored: # ----  Kifu for Windows V6.00 棋譜ファイル  ----
Ignored: 開始日時：2007/03/21
Ignored: 終了日時：2007/03/21
Ignored: 持ち時間：２時間
Ignored: 棋戦：大和証券杯特別対局
Ignored: 戦型：四間飛車
Ignored: 場所：品川プリンスホテル，クラブ eX
Ignored: 手合割：平手　　
Ignored: 先手：Bonanza
Ignored: 後手：渡辺明
Ignored: 手数----指手---------消費時間--
Move   :    1 ７六歩(77)   ( 0:01/00:00:01)
Move   :    2 ８四歩(83)   ( 2:32/00:02:32)
Move   :    3 ６六歩(67)   ( 0:01/00:00:02)
Move   :    4 ３四歩(33)   ( 0:11/00:02:43)
Move   :    5 ６八飛(28)   ( 0:01/00:00:03)
Move   :    6 ６二銀(71)   ( 0:14/00:02:57)
Move   :    7 ４八玉(59)   ( 0:01/00:00:04)
Move   :    8 ５四歩(53)   ( 0:17/00:03:14)
Move   :    9 ３八玉(48)   ( 0:01/00:00:05)
Move   :   10 ４二玉(51)   ( 0:20/00:03:34)
```

指し手と判定すれば　`Move   :`、 それ以外は `Ignored:` を頭に付けて　デバッグライト（debug write）　する。

手数も消費時間も　今回は欲しくないので、

```
    1 ７六歩(77)   ( 0:01/00:00:01)
```

ではなく

```
７六歩(77)
```

だけ欲しい。

```疑似コード
"\s*\d+ ((.*))*\([ 0-9:/]+\)"
group[1]
```

ぐらい書けば　引っこ抜いてくれそうだが、実装的な説明は省略して　そういうことは　できたものとして話しを進める。
正規表現の説明は、めんどくせ、Debuggex 見て知れ。

[正規表現チェックツールまとめ](https://qiita.com/aqril_1132/items/c185c7ad84c129e5a2df)

正規表現を簡単に書くテクニックは、

```
.*(.*)?.*
```

この形から、

```
.*\s*(.*)?.*
```

```
.*\s*\d+(.*)?.*
```

と、正規表現をグラフィカルに見れるツールの画面を見ながら　マッチするところを広げていけだぜ。

あっ！

```
  50 ４二金(32)   ( 0:40/00:13:43)
  51 ６六角打     ( 2:15/00:39:01)
```

打のとき　丸かっこ　が外れるのか。

```
  47 ６五歩(66)   ( 2:07/00:36:45)
  48 ７七角成(33) ( 1:14/00:13:03)
  49 同　桂(89)   ( 0:01/00:36:46)
  50 ４二金(32)   ( 0:40/00:13:43)
  51 ６六角打     ( 2:15/00:39:01)
```

周りを見ると　けっこう　書式がばらばらだ。

```
  80 ３九成香(49) ( 0:55/01:01:22)
```

駒の名前も１文字だけではなく　`成香`　のように２文字のもある。

```
    Finished dev [unoptimized + debuginfo] target(s) in 1.90s
     Running `target\debug\examples\eat_a_kif.exe --path C:/muzudho/kifuwarabe-wcsc29-learn/output-wcsc-record/copied-daiwa/daiwa.kif`
args.path = 'C:/muzudho/kifuwarabe-wcsc29-learn/output-wcsc-record/copied-daiwa/daiwa.kif'.
Ignored: # ----  Kifu for Windows V6.00 棋譜ファイル  ----
Ignored: 開始日時：2007/03/21
Ignored: 終了日時：2007/03/21
Ignored: 持ち時間：２時間
Ignored: 棋戦：大和証券杯特別対局
Ignored: 戦型：四間飛車
Ignored: 場所：品川プリンスホテル，クラブ eX
Ignored: 手合割：平手　　
Ignored: 先手：Bonanza
Ignored: 後手：渡辺明
Ignored: 手数----指手---------消費時間--
７六歩(77)
８四歩(83)
６六歩(67)
３四歩(33)
６八飛(28)
６二銀(71)
４八玉(59)
５四歩(53)
３八玉(48)
４二玉(51)
```

欲しいところが、欲しいところだけ　取れるようになる。ここまでが　シンタックスパース（構文解析）、レキシカルパース（字句解析）フェーズだぜ。
だが、このままでは使いにくい。

```
６五歩(66)
７七角成(33)
同　桂(89)
４二金(32)
６六角打
```

この変化を　１パターンに　おさめておきたい。

```
筋  段  同  駒  成  打  (  筋  段  )
--  --  --  --  --  --  -  -  -  -
６  五      歩          (   6  6  )
７  七      角  成      (   3  3  )
        同　桂          (   8  9  )
６  六      角      打
```

分解すると　こうなるようだ。

```
Phase   0  1   2   3   4   5  6   7  8  9
       筋  段  同  駒  成  打  (  筋  段  )
       --  --  --  --  --  --  -  -  -  -
       ６  五      歩          (   6  6  )
       ７  七      角  成      (   3  3  )
               同　桂          (   8  9  )
       ６  六      角      打
```

フェーズの番号を振ると 10。　逐次　マッチングしていけばいい。

```:疑似コード
struct Sasite {
    file : number,
    rank : number,
    same : bool,
    piece : string,
    promote : bool,
    drop : bool,
    file2 : number,
    rank2 : number,
}
```

説明するのがめんどくさいので　こういう適当な入れ物を作っておくと思えだぜ。
この入れ物を埋めていけばいい。

疑似コード書くの　めんどくさいので Rust言語そのまま張り付ける。

```rust
use parser::*;
use piece_etc::*;
use regex::Regex;

pub struct KifMove {
    pub dst_file : i8,
    pub dst_rank : i8,
    pub is_same : bool,
    pub piece : String,
    pub is_promote : bool,
    pub is_drop : bool,
    pub src_file : i8,
    pub src_rank : i8,
}
impl KifMove {
    pub fn to_sign(&self) -> String {
        let mut sign = "".to_string();

        sign = format!("{} {}", sign, self.dst_file);
        sign = format!("{} {}", sign, self.dst_rank);
        sign = format!("{} {}", sign, self.is_same);
        sign = format!("{} {}", sign, self.piece);
        sign = format!("{} {}", sign, self.is_promote);
        sign = format!("{} {}", sign, self.is_drop);
        sign = format!("{} {}", sign, self.src_file);
        sign = format!("{} {}", sign, self.src_rank);

        sign
    }

    pub fn parse(line:&str) -> Option<KifMove> {
        let re = Regex::new(r"\s*\d+ ((.*))*\([ 0-9:/]+\)").unwrap();
        let caps = re.captures(line).unwrap();
        let sign = caps.get(1).map_or("", |m| m.as_str());
        print!("{} -> ", sign);

        let mut mv = KifMove {
            dst_file : 0,
            dst_rank : 0,
            is_same : false,
            piece : "".to_string(),
            is_promote : false,
            is_drop : false,
            src_file : 0,
            src_rank : 0,
        };

        /**
Phase   0  1   2   3   4   5  6   7  8  9
       筋  段  同  駒  成  打  (  筋  段  )
       --  --  --  --  --  --  -  -  -  -
       ６  五      歩          (   6  6  )
       ７  七      角  成      (   3  3  )
               同　桂          (   8  9  )
       ６  六      角      打
         */
        let mut nth = 0;

        if 0 < sign.len() - nth {
            // Phase 0.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            mv.dst_file = match ch.as_str() {
                "１" => 1,
                "２" => 2,
                "３" => 3,
                "４" => 4,
                "５" => 5,
                "６" => 6,
                "７" => 7,
                "８" => 8,
                "９" => 9,
                _ => {nth -= 1; 0},
            };
        }

        if 0 < sign.len() - nth {
            // Phase 1.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            mv.dst_rank = match ch.as_str() {
                "一" => 1,
                "二" => 2,
                "三" => 3,
                "四" => 4,
                "五" => 5,
                "六" => 6,
                "七" => 7,
                "八" => 8,
                "九" => 9,
                _ => {nth -= 1; 0},
            };
        }

        if 0 < sign.len() - nth {
            // Phase 2.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            mv.is_same = match ch.as_str() {
                "同" => {
                    let ch = sign.chars().nth(nth).unwrap().to_string();
                    nth += 1;
                    match ch.as_str() {
                        "　" => true,
                        _ => panic!("Unexpected same ch: '{}'.", ch),
                    }
                },
                _ => {nth -= 1; false},
            };
        }

        if 0 < sign.len() - nth {
            // Phase 3.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            mv.piece = match ch.as_str() {
                "歩" => "歩",
                "香" => "香",
                "桂" => "桂",
                "銀" => "銀",
                "金" => "金",
                "玉" => "玉",
                "角" => "角",
                "飛" => "飛",
                "と" => "と",
                "馬" => "馬",
                "龍" => "龍",
                "成" => {
                    let ch = sign.chars().nth(nth).unwrap().to_string();
                    nth += 1;
                    match ch.as_str() {
                        "香" => "成香",
                        "桂" => "成桂",
                        "銀" => "成銀",
                        "金" => "成金",
                        _ => panic!("Unexpected promoted ch: '{}'.", ch),
                    }
                },
                _ => {nth -= 1; ""},
            }.to_string();
        }

        if 0 < sign.len() - nth {
            // Phase 4.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            mv.is_promote = match ch.as_str() {
                "成" => true,
                _ => {nth -= 1; false},
            };
        }

        if 0 < sign.len() - nth {
            // Phase 5.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            mv.is_drop = match ch.as_str() {
                "打" => true,
                _ => {nth -= 1; false},
            };
        }

        if 0 < sign.len() - nth {
            // Phase 6.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            match ch.as_str() {
                "(" => {},
                _ => {nth -= 1},
            };
        }

        if 0 < sign.len() - nth {
            // Phase 7.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            mv.src_file = match ch.as_str() {
                "1" => 1,
                "2" => 2,
                "3" => 3,
                "4" => 4,
                "5" => 5,
                "6" => 6,
                "7" => 7,
                "8" => 8,
                "9" => 9,
                _ => {nth -= 1; 0},
            };
        }

        if 0 < sign.len() - nth {
            // Phase 8.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            mv.src_rank = match ch.as_str() {
                "1" => 1,
                "2" => 2,
                "3" => 3,
                "4" => 4,
                "5" => 5,
                "6" => 6,
                "7" => 7,
                "8" => 8,
                "9" => 9,
                _ => {nth -= 1; 0},
            };
        }

        if 0 < sign.len() - nth {
            // Phase 9.
            let ch = sign.chars().nth(nth).unwrap().to_string();
            nth += 1;
            match ch.as_str() {
                ")" => {},
                _ => {nth -= 1},
            };
        }

        Some(mv)
    }
}
```

だいたい動くだろ。

```
同　飛(27)    ->  :  0 0 true 飛 false false 2 7
１五金打      ->  :  1 5 false 金 false true 0 0
２四歩打      ->  :  2 4 false 歩 false true 0 0
２六金(15)    ->  :  2 6 false 金 false false 1 5
２三歩成(24)  ->  :  2 3 false 歩 true false 2 4
同　金(32)    ->  :  0 0 true 金 false false 3 2
２四歩打      ->  :  2 4 false 歩 false true 0 0
２七歩打      ->  :  2 7 false 歩 false true 0 0
２三歩成(24)  ->  :  2 3 false 歩 true false 2 4
３九龍(99)    ->  :  3 9 false 龍 false false 9 9
２二と(23)    ->  :  2 2 false と false false 2 3
同　角(33)    ->  :  0 0 true 角 false false 3 3
３九銀(28)    ->  :  3 9 false 銀 false false 2 8
２八金打      ->  :  2 8 false 金 false true 0 0
同　銀(39)    ->  :  0 0 true 銀 false false 3 9
同　歩成(27)  ->  :  0 0 true 歩 true false 2 7
同　馬(91)    ->  :  0 0 true 馬 false false 9 1
２七歩打      ->  :  2 7 false 歩 false true 0 0
同　馬(28)    ->  :  0 0 true 馬 false false 2 8
同　金(26)    ->  :  0 0 true 金 false false 2 6
３九銀打      ->  :  3 9 false 銀 false true 0 0
３八銀打      ->  :  3 8 false 銀 false true 0 0
２八金打      ->  :  2 8 false 金 false true 0 0
同　金(27)    ->  :  0 0 true 金 false false 2 7
同　銀(39)    ->  :  0 0 true 銀 false false 3 9
２七歩打      ->  :  2 7 false 歩 false true 0 0
```

ぱっと見た感じ　それっぽい感じで取れている。
ここらへんは　まだ　レキシカルパース（字句解析）☆

```
２八金打      ->  :  2 8 false 金 false true 0 0
同　銀(39)    ->  :  0 0 true 銀 false false 3 9
同　歩成(27)  ->  :  0 0 true 歩 true false 2 7
同　馬(91)    ->  :  0 0 true 馬 false false 9 1
```

`同 ` が連続するところを見ていただきたい。
同は　何を示している　代わりの記号なのか。 というと　同が始まる最初の１個前の　２八　をずっと続けることだが、

レキシカルパースの中で　一緒に　`同 ` も解決してしまうのではなく、
レキシカルパースの結果を使って　`同 ` を解決することにする。
その理由としては、バグったときに　ここまではバグっていない、ここから先でバグった、と問題を切り分けることができて　バグを探しやすい　ことだぜ。

```rust
        // '同'を解決する。
        let mut pre_file = 0;
        let mut pre_rank = 0;
        for mov in &mut record.items {
            if mov.is_same {
                mov.dst_file = pre_file;
                mov.dst_rank = pre_rank;
            }

            pre_file = mov.dst_file;
            pre_rank = mov.dst_rank;

            println!("Move : {}", mov.to_sign());
        }
```

実行速度を優先するのは　実行速度が遅いと思ってるやつが　完成しているときでいい。

```
Move :  6 7 false 飛 false false 6 8
Move :  8 6 false 歩 false false 8 5
Move :  8 6 true 歩 false false 8 7
Move :  8 6 true 飛 false false 8 3
Move :  9 1 false 角 true false 4 6
Move :  8 9 false 飛 true false 8 6
Move :  4 4 false 歩 false false 4 5
Move :  4 4 true 銀 false false 5 3
Move :  4 7 false 飛 false false 6 7
Move :  9 9 false 龍 false false 8 9
Move :  6 4 false 歩 false false 6 5
Move :  3 5 false 銀 false false 4 4
Move :  3 6 false 歩 false false 3 7
Move :  4 6 false 香 false true 0 0
Move :  3 7 false 飛 false false 4 7
Move :  2 6 false 銀 false false 3 5
Move :  2 6 true 銀 false false 2 7
Move :  4 9 false 香 true false 4 6
Move :  6 3 false 歩 true false 6 4
Move :  3 9 false 成香 false false 4 9
Move :  3 9 true 金 false false 3 8
Move :  2 5 false 歩 false false 2 4
Move :  2 5 true 銀 false false 2 6
Move :  2 7 false 歩 false true 0 0
Move :  2 7 true 飛 false false 3 7
Move :  2 6 false 歩 false true 0 0
Move :  2 6 true 飛 false false 2 7
Move :  1 5 false 金 false true 0 0
Move :  2 4 false 歩 false true 0 0
Move :  2 6 false 金 false false 1 5
```

ぱっと見た感じ、
３列目が true になっている行は、１列目、２列目の数が　上の行と同じになっているだろ。
このように　テストをしながら
できている　ところ
を増やして　進めていくのが開発だぜ。

これだけ変換できれば　将棋の駒を動かすことができる。　棋譜の解析はおわり。
途中っぽいが　終わり。

# 保存形式のコンバーターはどうやって作るかだって？

将棋盤で駒を動かせるんだったら　棋譜　は作れるだろ。
好きな形式のファイルを読み込んで、好きな形式のファイルで保存しろだぜ。
べつに　どの形式から　どの形式へ直接コンバートするっぽいやつは作らなくていい。ピボット！