C#でGPPG/GPLEXを使って電卓を作成する

# 概要
c言語ならyacc/lex、c++ならbison/flexがあるように、c#にはGPPG/GPLEXがあります。
これを使えば、字句解析/構文解析が可能です。
昔は導入が面倒だったのですが、いつからかNuGetで簡単に導入できるようになりました。
今回はサンプルとして電卓を作りたいと思います。

# サンプルコード
以下に実際に動作するコードを置いてます。
https://github.com/minoru-nagasawa/GPPGCalculator

# 作成方法
## 1. プロジェクトを作成
今回はコンソールアプリで作ります。
名前はGPPGCalculatorとします。
.NET Coreは未対応ですので、.NET Frameworkにしてください。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/178867/76cf94ea-cba5-5657-38ef-db3cdcf01010.png)

## 2. NuGetでYaccLexToolsをインストール
検索で「YaccLex」や「GPPG」を入力すれば出てきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/178867/3d817f32-3210-30ca-c67d-457ece01d0ac.png)

## 3. サンプル電卓用のソースコードを自動生成
GPPG/GPLEXには、サンプル電卓のソースコードを自動生成する機能があるので使います。
パッケージマネージャーコンソールから「Add-CalculatorExample」と入力してください。
これにより、Calculator.Language.grammar.yなどが自動生成されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/178867/ba943c4a-4f37-46ea-8179-2837cb9e0792.png)

パッケージマネージャコンソールが見つからない場合は、右上のクイック起動で「パッケージ」と検索すれば簡単です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/178867/27d0ae1e-5d9f-29a4-7e40-d599503d27ab.png)

## 4. Mainを変更
なんと！ あとはMainから呼び出すようにすれば完成です。

```c#:Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GPPGCalculator
{
    class Program
    {
        static void Main(string[] args)
        {
            // 作った電卓のParserを生成する。
            // 字句解析器は、この中で生成されている。
            var parser = new Calculator.CalculatorParser();

            do
            {
                // 式を入力。空文字なら終了。
                Console.Write("> ");
                var input = Console.ReadLine();
                if (string.IsNullOrEmpty(input))
                {
                    return;
                }

                // 式を解析。この中で結果も出力される。
                parser.Parse(input);
            } while (true);
        }
    }
}
```

# 実行例
数式を入力すると、演算結果を出力します。
構文解析の様子も出力されます。

```
> 1+1
token: 1
Rule -> number: 1
Rule -> factor: 1
Rule -> term: 1
token: +
Rule -> exp: 1
token: 1
Rule -> number: 1
Rule -> factor: 1
Rule -> term: 1
Rule -> exp: 1 + 1
result is 2

> 1 + 2 * 3
token: 1
Rule -> number: 1
Rule -> factor: 1
Rule -> term: 1
token: +
Rule -> exp: 1
token: 2
Rule -> number: 2
Rule -> factor: 2
Rule -> term: 2
token: *
token: 3
Rule -> number: 3
Rule -> factor: 3
Rule -> term: 2 * 3
Rule -> exp: 1 + 6
result is 7
```

# 補足
自動生成される字句解析と構文解析のコードを以下に貼り付けておきます。

```Calculator.Language.analyzer.lex
%namespace Calculator
%scannertype CalculatorScanner
%visibility internal
%tokentype Token

%option stack, minimize, parser, verbose, persistbuffer, noembedbuffers 

Eol             (\r\n?|\n)
NotWh           [^ \t\r\n]
Space           [ \t]
Number          [0-9]+
OpPlus			\+
OpMinus			\-
OpMult			\*
OpDiv			\/
POpen			\(
PClose			\)

%{

%}

%%

{Number}		{ Console.WriteLine("token: {0}", yytext);		GetNumber(); return (int)Token.NUMBER; }

{Space}+		/* skip */

{OpPlus}		{ Console.WriteLine("token: {0}", yytext);		return (int)Token.OP_PLUS; }
{OpMinus}		{ Console.WriteLine("token: {0}", yytext);		return (int)Token.OP_MINUS; }
{OpMult}		{ Console.WriteLine("token: {0}", yytext);		return (int)Token.OP_MULT; }
{OpDiv}			{ Console.WriteLine("token: {0}", yytext);		return (int)Token.OP_DIV; }
{POpen}			{ Console.WriteLine("token: {0}", yytext);		return (int)Token.P_OPEN; }
{PClose}		{ Console.WriteLine("token: {0}", yytext);		return (int)Token.P_CLOSE; }

%%
```

``` Calculator.Language.grammar.y
%namespace Calculator
%partial
%parsertype CalculatorParser
%visibility internal
%tokentype Token

%union { 
			public int n; 
			public string s; 
	   }

%start line

%token NUMBER, OP_PLUS, OP_MINUS, OP_MULT, OP_DIV, P_OPEN, P_CLOSE

%%

line   : exp							{ Console.WriteLine("result is {0}\n", $1.n);}
       ;

exp    : term                           { $$.n = $1.n;			Console.WriteLine("Rule -> exp: {0}", $1.n); }
       | exp OP_PLUS term               { $$.n = $1.n + $3.n;	Console.WriteLine("Rule -> exp: {0} + {1}", $1.n, $3.n); }
       | exp OP_MINUS term              { $$.n = $1.n - $3.n;	Console.WriteLine("Rule -> exp: {0} - {1}", $1.n, $3.n); }
       ;

term   : factor							{$$.n = $1.n;			Console.WriteLine("Rule -> term: {0}", $1.n); }
       | term OP_MULT factor            {$$.n = $1.n * $3.n;	Console.WriteLine("Rule -> term: {0} * {1}", $1.n, $3.n); }
       | term OP_DIV factor             {$$.n = $1.n / $3.n;	Console.WriteLine("Rule -> term: {0} / {1}", $1.n, $3.n); }
       ; 

factor : number                         {$$.n = $1.n;			Console.WriteLine("Rule -> factor: {0}", $1.n); }
       | P_OPEN exp P_CLOSE             {$$.n = $2.n;			Console.WriteLine("Rule -> factor: ( {0} )", $3.n);}
       ;

number : 
       | NUMBER							{ Console.WriteLine("Rule -> number: {0}", $1.n); }
       ;

%%
```

# 参考URL
https://github.com/ernstc/YaccLexTools