LoginSignup
3
7

More than 5 years have passed since last update.

C#でGPPG/GPLEXを使って電卓を作成する(応用編)

Last updated at Posted at 2019-01-14

概要

GPPG/GPLEXを使えば、C#で字句解析器/構文解析器を簡単に作成できます。
前回は自動生成で電卓を作りましたが、今回はもう少し踏み込んで電卓を作りたいと思います。
https://qiita.com/minoru-nagasawa/items/8c188135ab131b7fbedc

サンプルコード

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

実行結果

今回は変数を使える電卓を作ります。
以下のように実行できます。

> a = 3
3
> b = 5
5
> a+b
8
>

作成方法

1. プロジェクトを作成

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

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

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

3. 字句解析器(Lexer)と構文解析器(Parser)の生成

パッケージマネージャーコンソールから「Add-Parser Xxxxx」と入力してください。
XxxxxScannerとXxxxxParserが生成されます。
今回はCalculatorとします。

PM> Add-Parser Calculator

実行するとポップアップが出ますので「ソリューションの再読み込み」を押してください。
image.png

4. 字句解析器の変更

以下のように変更します。

Calculator.Language.analyzer.lex
// ---------------------------
// 定義部
// ---------------------------

// 出力するクラスのnamespace
%namespace GPPGCalculator2

// 出力するクラス名
%scannertype CalculatorScanner

// 出力するクラスのアクセシビリティ
%visibility internal

// トークンに使用するenumの型名
%tokentype Token

// オプション
// stack          : これを付けると、状態をスタックに保存できるようになる。
//                  C言語のコメント(/*  */)の解釈のように、状態を管理したい場合に使用する。
//                  電卓では不要だが付けておく。
// minimize       : これを付けると、内部構造のDFSA(決定性有限オートマトン)を最小化してくれる
// verbose        : これを付けると、ビルドの途中経過が出力される
// persistbuffer  : これを付けると、入力を全てバッファに格納してから解析するようになる。
//                  それにより、ScanBuff.GetString()で任意の位置から読めるようになる。
//                  しかし、入力ファイルのサイズが大きいと、メモリ使用量も増えるので注意が必要となる。
// noembedbuffers : これを付けると、バッファとしてGplexBuffersクラスを使うようになる。
//                  これにより、アプリ側でバッファを利用したい場合に便利になる。
//                  付けないと、バッファにアクセスできない。
%option stack, minimize, parser, verbose, persistbuffer, noembedbuffers

D           [0-9]
L           [a-zA-Z_]
H           [a-fA-F0-9]
E           [Ee][+\-]?{D}+

%{

%}


%%
// ---------------------------
// ルール部
// ---------------------------

// 変数名
{L}({L}|{D})*               { yylval.text = yytext; return (int)Token.VARIABLE; }

// 16進数
0[xX]{H}+                   { yylval.real = (double)Convert.ToInt32(yytext, 16); return (int)Token.CONSTANT; }

// 8進数
0{D}+                       { yylval.real = (double)Convert.ToInt32(yytext, 8);  return (int)Token.CONSTANT; }

// 10進数の整数
{D}+                        { yylval.real = double.Parse(yytext); return (int)Token.CONSTANT; }

// 指数表示の10進数の整数
{D}+{E}                     { yylval.real = double.Parse(yytext); return (int)Token.CONSTANT; }

// 実数
{D}*"."{D}+({E})?           { yylval.real = double.Parse(yytext); return (int)Token.CONSTANT; }
{D}+"."{D}*({E})?           { yylval.real = double.Parse(yytext); return (int)Token.CONSTANT; }

// 演算子
"="                         { return '='; }
"("                         { return '('; }
")"                         { return ')'; }
"-"                         { return '-'; }
"+"                         { return '+'; }
"*"                         { return '*'; }
"/"                         { return '/'; }

// 該当しない文字は無視する
.                           /* Skip */


%%
// ---------------------------
// コード部
// ---------------------------

Calculator.Scanner.cs
// 不要な関数を削除したぐらいで大きな変更なし
using System;
using System.Collections.Generic;
using System.Text;

namespace GPPGCalculator2
{
    internal partial class CalculatorScanner
    {
        public override void yyerror(string format, params object[] args)
        {
            base.yyerror(format, args);
            Console.WriteLine(format, args);
            Console.WriteLine();
        }
    }
}

5. 構文解析器の変更

以下のように変更します。

Calculator.Language.grammer.y
// ---------------------------
// 宣言部
// ---------------------------

// 出力するクラスのnamespace
%namespace GPPGCalculator2

// 生成するクラスがpartialクラスになる
// そうすることで、実装部分を*.yではなく、*.csに書けるようになるので便利
%partial

// 出力するクラス名
%parsertype CalculatorParser

// 出力するクラスのアクセシビリティ
%visibility internal

// トークンに使用するenumの型名
%tokentype Token

%union { 
    public double real;
    public string text;
}

%token <text> VARIABLE
%token <real> CONSTANT

%start main


%%
// ---------------------------
// ルール部
// ---------------------------

main            : assignment                    { m_result = $1.real; }
                ;

assignment      : additive                      { $$.real = $1.real; }
                | VARIABLE '=' assignment       {
                                                    m_variables[$1] = $3.real;
                                                    $$.real = $3.real;
                                                }
                ;

additive        : multiplicative                { $$.real = $1.real;           }
                | additive '+' multiplicative   { $$.real = $1.real + $3.real; }
                | additive '-' multiplicative   { $$.real = $1.real - $3.real; }
                ;

multiplicative  : primary                       { $$.real = $1.real;            }
                | multiplicative '*' primary    { $$.real = $1.real * $3.real;  }
                | multiplicative '/' primary    { $$.real = $1.real / $3.real;  }
                ; 

primary         : VARIABLE                      {
                                                    double v;
                                                    if (m_variables.TryGetValue($1, out v))
                                                    {
                                                        $$.real = v;
                                                    }
                                                    else
                                                    {
                                                        $$.real = 0;
                                                    }
                                                }
                | CONSTANT                      { $$.real = $1; }
                | '(' additive ')'              { $$.real = $2.real; }
                ;


%%
// ---------------------------
// コード部
// ---------------------------

Calculator.Parser.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace GPPGCalculator2
{
    internal partial class CalculatorParser
    {
        // Parseした結果が格納される。
        // 格納する処理は、Calculator.Language.grammer.yの中に記述している。
        private double m_result;

        // 変数を格納するためのディクショナリ
        // Keyが変数名、Valueが格納している値
        private Dictionary<string, double> m_variables = new Dictionary<string, double>();

        public CalculatorParser() : base(null) { }

        public bool Parse(string s, out double result)
        {
            result = 0;

            byte[] inputBuffer = System.Text.Encoding.Default.GetBytes(s);
            using (var stream = new MemoryStream(inputBuffer))
            {
                this.Scanner = new CalculatorScanner(stream);
                bool rc = this.Parse();
                if (rc)
                {
                    result = m_result;
                }
                return rc;
            }
        }
    }
}

6. Mainを変更

Mainから呼び出すようにすれば完成です。

Program.cs
using System;

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

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

                // 式を解析して結果を出力
                double result;
                bool rc = parser.Parse(input, out result);
                if (rc)
                {
                    Console.WriteLine(result.ToString());
                }
            } while (true);
        }
    }
}

参考URL

gplexのドキュメント
オリジナルが見つからなかったので、Internet Archiveを指してます。
https://web.archive.org/web/20120325033719/http://plas.fit.qut.edu.au/gplex/files/gplex.pdf

gppgのドキュメント
オリジナルが見つからなかったので、Internet Archiveを指してます。
https://web.archive.org/web/20120325042446/http://plas.fit.qut.edu.au/gppg/files/gppg.pdf

3
7
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
7