LoginSignup
3
10

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-01-09

概要

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

サンプルコード

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

作成方法

1. プロジェクトを作成

今回はコンソールアプリで作ります。
名前はGPPGCalculatorとします。
.NET Coreは未対応ですので、.NET Frameworkにしてください。
image.png

2. NuGetでYaccLexToolsをインストール

検索で「YaccLex」や「GPPG」を入力すれば出てきます。
image.png

3. サンプル電卓用のソースコードを自動生成

GPPG/GPLEXには、サンプル電卓のソースコードを自動生成する機能があるので使います。
パッケージマネージャーコンソールから「Add-CalculatorExample」と入力してください。
これにより、Calculator.Language.grammar.yなどが自動生成されます。
image.png

パッケージマネージャコンソールが見つからない場合は、右上のクイック起動で「パッケージ」と検索すれば簡単です。
image.png

4. Mainを変更

なんと! あとはMainから呼び出すようにすれば完成です。

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

3
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
10