Ironyを使ってみよう

#Ironyとは
IronyはC#で使えるパーサージェネレータです。
パーサージェネレータとはなんぞや、という方もいると思うので少し解説を。
パーサージェネレータとは、文字通り構文解析器を作るためのライブラリやツールのことです。有名どころではyaccとか、javaccとかがありますね。
もちろんXMLやJSONなどをパースするくらいならそのためのライブラリがあるでしょうし、なくてもぱっと書けるでしょう。ですが往々にしてテキストをパースしたいニーズはあるものです。パーサーをいちから作るのは大変、でも必要。そういうときに活躍するのがパーサージェネレータです。
しかしです。javaccはjavaのソースを、yaccはCのソースしか吐いてくれません。不便な(当たり前です)。
その上、採用する言語が変わると使えるパーサージェネレータが変わってしまうので、そのジェネレータ毎にBNF記法が一部違っていたり、はたまた全然別物だったりするわけです。
IronyはBNFの記法を覚えず、C#の文法で構文の定義ができます。極論C#が使えるならば<b><i>Ironyも使える</i></b>ということです！
しかしながら残念なことに日本語のドキュメントが無く、また日本語の情報も少ないので、些か手を出しづらいかもしれません。そこで今回から数回にわたってIronyの解説をしたいと思います。
#早速使ってみよう！
まず、Visual Studioのプロジェクトを作りましょう。
ConsoleApplicationをテンプレートに選んで作ったら、NuGetでIronyとIrony.Interpreterを追加します。
そしてParser/GrammarDefinition.csを追加しましょう。

```csharp:GrammarDefinition.cs
using Irony.Parsing;

namespace MyLangParser.Parser{
    [Language("MyLangGrammar")]
    class GrammarDefinition : Grammar { }
}
```
ここまでできたら、一度保存します。
#文法を考える
パーサーであるからには、何らかの文法に則ってテキストをパースするものであるはずです。
いくら優れたパーサージェネレータがあっても、文法を考えずしてパーサーを作ることはできません。
文法を考えましょう。
今回は、コンフィグファイルのパーサーを作ります。
<code>Entry1 = "foo"
Section1{
&ensp;&ensp;&ensp;&ensp;Id1 = "abc"
&ensp;&ensp;&ensp;&ensp;Subsection{
&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;hoge = 12
&ensp;&ensp;&ensp;&ensp;}
}
Section2{
&ensp;&ensp;&ensp;&ensp;foo = "It will fine!"
}</code>
こんな感じの書き方ができるような文法を考えます。
<code>value  =:: &lt;Number&gt; | &lt;String&gt;
entry  =:: &lt;identifier&gt;'='&lt;value&gt;
section =:: &lt;identifier&gt;'{'((&lt;entry&gt; | &lt;section&gt;)\*)'}'
config =:: (&lt;section&gt; | &lt;entry&gt;)\*</code>
こんな感じになるでしょうか。
&lt;config&gt;と&lt;module&gt;の中身は一緒の内容なので、まとめるとこんな感じになります。
<code>value  =:: &lt;Number&gt; | &lt;String&gt;
entry  =:: &lt;identifier&gt;'='&lt;value&gt;
module =:: &lt;identifier&gt;'{'&lt;config&gt;\*'}'
config =:: (&lt;module&gt; | &lt;entry&gt;)\*</code>
これをIronyのコードにしてみましょう。
このコードはGrammarDefinition.csの中の、GrammarDefinitionクラスの中に記述します。

```csharp:GrammarDefinition
    public GrammarDefinition(){
        ///トークン、非終端記号の宣言
        var Num = new NumberLiteral("Number");
        var Str = new StringLiteral("String", "\"");
        var Id = new IdentifierTerminal("identifier");
        var Value = new NonTerminal("Value");
        var Entry = new NonTerminal("Entry");
        var Section = new NonTerminal("Section");
        var Config = new NonTerminal("Config");
        
        ///文法の定義
        Value.Rule = Num | Str;
        Entry.Rule = Id + ToTerm("=") + Value;
        Section.Rule = Id + ToTerm("{") + Config + ToTerm("}");
        Config.Rule = MakeStarRule(Config, Section | Entry);
        
        Root = Config;
    }
```
#文法のデバッグ
[https://github.com/IronyProject/Irony](https://github.com/IronyProject/Irony)のリポジトリをクローンするかzipとしてダウンロードします。
zipとしてダウンロードした場合は適当なディレクトリに解凍します。
コンパイルにmsbuild.exeを使うので、パスが通っていることを確認します。

```bash:パスが通っていた場合
Microsoft Windows [Version 10.0.15063]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\>where msbuild
C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe
```
このように入力するとパスが通っている場合、例のようにmsbuild.exeまでのパスが表示されます。

```bash:パスが通っていなかった場合
Microsoft Windows [Version 10.0.15063]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\>where msbuild
情報: 与えられたパターンのファイルが見つかりませんでした。
```

このように表示された場合はVisual Studioを使っている場合おそらくどこかにありますので探してパスに追加してください。
正常にパスが通ったならビルドに入りましょう！

```bash:構文規則確認ツールのビルド
C:\>cd "クローン又は解凍したフォルダ"
クローン又は解凍したフォルダ>cd .\Irony.GrammarExplorer
クローン又は解凍したフォルダ\Irony.GrammarExplorer>msbuild /t:Build /v:m 030.Irony.GrammarExplorer.2012.csproj
Microsoft (R) Build Engine バージョン <msbuildのバージョン>
Copyright (C) Microsoft Corporation. All rights reserved.

  010.Irony.2012 -> クローン又は解凍したフォルダ\Irony\bin\Debug\Irony.dll
  015.Irony.Interpreter.2012 -> クローン又は解凍したフォルダ\Irony.Interpreter\bin\Debug\Irony.Interpr
  eter.dll
  030.Irony.GrammarExplorer.2012 -> クローン又は解凍したフォルダ\Irony.GrammarExplorer\bin\Debug\Irony
  .GrammarExplorer.exe
クローン又は解凍したフォルダ\Irony.GrammarExplorer>
```
正常にビルドに成功すれば「クローン又は解凍したフォルダ\Irony.GrammarExplorer\bin\Debug\」にファイルが生成されます。そのうちの、FastColoredTextBox.dll、Irony.dll、Irony.GrammarExplorer.exe、Irony.Interpreter.dllをプロジェクトフォルダにコピーしましょう。
一旦VisualStudioに戻ってプロジェクトをビルドします。
ビルドしたら、Irony.GrammarExplorer.exeを起動して下の写真の矢印の部分をクリックして、出てきたドロップダウンメニューの「Add grammar...」を選択します。
![Irony Grammar Explorer.png](https://qiita-image-store.s3.amazonaws.com/0/209421/e86bfe6e-6240-ace2-1028-129bedfa1e8e.png)
GUIで選択できるファイルは\*.dllだけですが、直接入力で\*.exeも指定できます。
ビルドした結果のバイナリを選択すると、下の写真のようなポップアップが出ますが、何も考えずに「OK」で構いません。
![Select Grammars 2017_12_01 16_44_27.png](https://qiita-image-store.s3.amazonaws.com/0/209421/cea03ad4-5384-7f02-43f5-946449c16ff2.png)
正常に構文定義が読み込まれると、下の写真のようになります。
![Irony Grammar Explorer selected.png](https://qiita-image-store.s3.amazonaws.com/0/209421/4458289d-6dcf-cabe-1ed6-dd1479b319c3.png)
Testタブをクリックして、
<code>Entry1 = "foo"
Section1{
&ensp;&ensp;&ensp;&ensp;Id1 = "abc"
&ensp;&ensp;&ensp;&ensp;Subsection{
&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;hoge = 12
&ensp;&ensp;&ensp;&ensp;}
}
Section2{
&ensp;&ensp;&ensp;&ensp;foo = "It will fine!"
}</code>
このように入力してみます。Parseボタンをクリックすると、正常に定義できている場合、「Parse Tree」タブにトークンツリーが生成されるはずです。エラーが出たり、ツリーがおかしい場合は、構文定義を見直してください。

今回はここまでです。次回もよろしくお願いします。

参考:
[Irony - Language Implementation Kit](https://en.wikibooks.org/wiki/Irony_-_Language_Implementation_Kit)