質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

1回答

980閲覧

BNF法を利用して、通常コンパイラのように型をそろえることができるか。

saijyaku

総合スコア1

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2021/05/24 03:00

前提・実現したいこと

Unityを使ってコンパイラのようなものを作成しており、
利用者がプログラムを入力できるようにしている。
その際に、文字をひとつずつ読み取り、四則演算を行って
計算をすることができています。
BNF法を利用して計算をしています。

発生している問題・エラーメッセージ

このままだと、すべてがint型でしか計算ができなくなってしまうので、キャストや
floatなど別の型での計算結果がおかしくなってしまうという点です。

これを解決するためにfloatやdoubleなどの計算を別途用意していたのですが、
もっと別の計算方法があるのでは?と思っておりますが、思いつきません。

BNF法を利用して、普通のコンパイラのような計算はできないでしょうか。

##該当するプログラム

C#

1 2public class INT_Arithmetic 3{ 4 static int nowIndex = 0; 5 6 static List<string> substList; 7 8 static public string Check(List<string> list) 9 { 10 substList = list; 11 nowIndex = 0; 12 return eval1().ToString(); 13 } 14 15 static int eval1() 16 { 17 var value = eval2(); 18 while (true) 19 { 20 if (nowIndex == substList.Count) 21 { 22 return value; 23 } 24 if (substList[nowIndex] == "+") 25 { 26 nowIndex++; 27 value += eval2(); 28 } 29 else if (substList[nowIndex] == "-") 30 { 31 nowIndex++; 32 value -= eval2(); 33 } 34 else 35 break; 36 } 37 return value; 38 } 39 40 static int eval2() 41 { 42 var value = eval3(); 43 if (nowIndex == substList.Count) 44 { 45 return value; 46 } 47 if (substList[nowIndex] == "*") 48 { 49 nowIndex++; 50 value *= eval3(); 51 } 52 else if (substList[nowIndex] == "/") 53 { 54 nowIndex++; 55 value /= eval3(); 56 } 57 else if (substList[nowIndex] == "%") 58 { 59 nowIndex++; 60 value %= eval3(); 61 } 62 return value; 63 } 64 65 static int eval3() 66 { 67 if (substList[nowIndex] == "(") 68 { 69 nowIndex++; 70 var value = eval1(); 71 if (substList[nowIndex] != ")") 72 { 73 // 閉じがない 74 } 75 nowIndex++; 76 return value; 77 } 78 else 79 return number(); 80 } 81 82 static int number() 83 { 84 int value = 0; 85 double deciValue = 0.0f; 86 bool plasFlag = true; // falseはマイナス 87 bool decimalFlag = false; 88 int decCount = 10; 89 // 無限ループさせる 90 while (true) 91 { 92 if (substList.Count > nowIndex) 93 { 94 // 数値だった場合 95 if (int.TryParse(substList[nowIndex], out int result)) 96 { 97 // 小数点対応 98 if (decimalFlag) 99 { 100 deciValue = deciValue + (result / (double)decCount); 101 decCount *= 10; 102 nowIndex++; 103 continue; 104 } 105 else 106 { 107 value = result; 108 } 109 if (nowIndex < substList.Count - 1) 110 // 次の値が、小数点だったら 111 if (substList[nowIndex + 1] == ".") 112 { 113 decimalFlag = true; 114 nowIndex += 2; 115 continue; 116 } 117 break; 118 } 119 else 120 { 121 if (decimalFlag) 122 { 123 return (int)value; 124 } 125 // 数字ではないので、変数 126 // すでに定義されている変数なのか 127 object sv = DataTable.GetVariableValueData(substList[nowIndex]); 128 if (sv != null) 129 { 130 value = (int)sv; 131 break; 132 } 133 else 134 { 135 // マイナス・プラスの符号の可能性があり 136 switch (substList[nowIndex]) 137 { 138 case "+": 139 plasFlag = true; 140 break; 141 case "-": 142 plasFlag = false; 143 break; 144 default: 145 //エラー 146 break; 147 } 148 nowIndex++; 149 } 150 } 151 } 152 else 153 { 154 return value; 155 } 156 } 157 nowIndex++; 158 if (!plasFlag) 159 { 160 value *= -1; 161 } 162 return value; 163 } 164}

補足情報(FW/ツールのバージョンなど)

visualStudio 2019
unity 2020.2.2f

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

質問への回答

>BNF法を利用して、普通のコンパイラのような計算はできないでしょうか。

「BNF法」というのは「BNF記法」のことでしょうか? BNF記法のことだと仮定して回答しますね。

BNF記法は単に言語の文法を定義するための書き方であり、構文解析の手法とは関係ありません。
質問者の書かれたコードが何と言う手法を使っているのかというと、コードを見る限りでは「再帰下降法」に思えます。

では、再帰下降法で普通のコンパイラのような計算ができないのかというと、そんなことはありません。再帰下降法で作られたプログラミング言語も世の中にはそれなりの数が存在していると思います(存在をいちいち確認したわけではないですが、「再帰下降法で言語を作れ」、と言われたら普通に作れるので)。

本当の問題点

そもそもの問題は、intしか使えないがfloatなども使いたいということですよね?

となると、現状のコードをちょっと直すぐらいではなかなか難しいと思います。

Check(List<string> list) という関数が呼ばれると計算が実行されるという作りのようですが、引数のlistはどうやって作られているのでしょうか? listを作る時点で簡単な字句解析を行なっていると思うのですが、プログラミング言語というのは字句解析も重要なので、その辺のコードも見たかったです。
というか、空白文字で切り分けているだけなのでしょうか? もしそうなら、空白文字で切り分けるだけの字句解析は、あまり意味がないというか……きちんと字句解析器を書けば必要のない処理になります(詳しくは専門の書籍を読むことをお勧めします)。

修正方針(概略)

修正方針を簡単に説明していきます。

字句解析時点で、トークン(文法上の最小単位)に切り分ける

まずは計算は後回しにして、字句解析(読み込んだ文字列を文法上の最小単位に切り分ける)をしたほうが良いです。
トークンクラスの定義は例をあげると下記のような感じでしょうか。

csharp

1 enum TokenType { 2 Plus, 3 Minus, 4 Multiply, 5 Divid, 6 Int, 7 Float, 8 L_Paren, 9 R_Paren, 10 } 11 12 class Token { 13 public TokenType tokenType { get; } 14 public int intValue { get; } 15 public float floatValue { get; } 16 17 public Token(TokenType typ){ 18 tokenType = typ; 19 } 20 21 public Token(int value){ 22 tokenType = TokenType.Int; 23 intValue = value; 24 } 25 26 public Token(float value){ 27 tokenType = TokenType.Float; 28 floatValue = value; 29 } 30 }

で、ユーザー入力を一度、トークンに分割するわけです。

計算結果クラスを導入する

トークンに切り分けたら、後はTokenTypeで処理を振り分けられます。

ここで問題になるのは、計算結果もintやfloatなど型が違う場合があることです。
となると、計算結果も「現在の計算結果の型が何なのか」を把握出来たほうが良いですよね。

実際に計算結果クラスの例を書いてみます。

csharp

1 enum ValueType { 2 Int, 3 Float, 4 } 5 6 class ResultValue { 7 public ValueType valueType { get; } 8 public int intValue { get; } 9 public float floatValue { get; } 10 11 public ResultValue(int value){ 12 valueType = ValueType.Int; 13 intValue = value; 14 } 15 16 public ResultValue(float value){ 17 valueType = ValueType.Float; 18 floatValue = value; 19 } 20 }

こんな感じに計算結果を表すクラスを定義しておけば、実際の計算処理は、今書いたResultValueクラスに持っていけます。

csharp

1 class ResultValue { 2 // ...(略)... 3 4 public ResultValue Add(ResultValue val1, ResultValue val2){ 5 if(val1.valueType == ValueType.Int && val2.valueType == ValueType.Int){ 6 // int同志の足し算 7 return new ResultValue(val1.intValue + val2.intValue); 8 }else if( ... ){ // int + float や float + int や float + float の処理も書く 9 10 11 } 12 } 13 }

このように、計算結果の現在の型が何なのかを常に把握できるようにしておけば、intとfloatなどを区別して処理できるようになります。

結論

ここまで書いておいてアレですが、インタープリターやコンパイラなどのプログミング言語処理系の実装は、きちんと説明すると本が1冊書けてしまう量になりますし、言語処理系の作成方法はかなり体系化されているので、一度、専門書を読むことを強くお勧めします。
ネット上にも、インタープリタ作成やコンパイラ作成の入門記事や「作ってみた」系の記事があふれていますので、まずはネットで学ぶのもありです。

投稿2021/05/26 01:31

JunSuzukiJapan

総合スコア314

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

saijyaku

2021/05/26 02:20

ご回答ありがとうございます。 >「BNF法」というのは「BNF記法」のことでしょうか? 失礼いたしました。はい、BNF記法の事です。 >引数のlistはどうやって作られているのでしょうか? こちらも初めに構文解析し 中身としては各要素にイメージとして 例)1+2*(1+1) [1] [+] [2] [*] [(] [1] [+] [1] [)] このように格納されるようになっています。 最小単位で切り分けるという事は現状だとできているかなと考えています。 ただ、それの型等は考慮していなかったので、考慮するようにしてみます。 計算結果の出す部分について、 この場合だと計算パターンをすべて用意しておかなければならないという事でしょうか。 例えば、string + string int + double など 一時期それも考えましたが、あまりにも膨大になりそうだと思い、実行していませんでした。 >一度、専門書を読むことを強くお勧めします。 おっしゃるとおりだと思います。 現在「プログラムはなぜ動くのか」著者:矢沢久雄さん の本を読み進めています。 後だしにはなってしまいますが、 「低レイヤを知りたい人のためのCコンパイラ作成入門」 https://www.sigbus.info/compilerbook#bnf%E3%81%AB%E3%82%88%E3%82%8B%E7%94%9F%E6%88%90%E8%A6%8F%E5%89%87%E3%81%AE%E8%A8%98%E8%BF%B0 こちらを少し参考にしました。 こちらにはBNFが採用されているようだったので、 私もこれ頼りにしていました。 今回回答いただいたものを参考に再度作り直しを検討させていただきたいと思います。 すぐにこちらを実装できるというのが保証できないため、今回はベストアンサーにさせていただきたいと思います。 丁寧なご回答ありがとうございます。
JunSuzukiJapan

2021/05/26 04:25

>こちらにはBNFが採用されているようだったので、私もこれ頼りにしていました。 なるほど。なんとなくわかった気がします。 言語を作る時、まずはじめにすることは、「言語の文法を考える」ことです。 あまりにも自明なことなので、「最初は文法を考えましょう!」なんて書いてある専門書はほぼないんだろうと思います。 で、考えた文法を、誰がみてもわかるように記述する方法がBNF記法なんです。 BNFで書かれた文法を、再帰下降法などで実装していくわけですが、この「文法を考える」段階と実装段階はわけて考えたほうが良いです。 専門書などでBNFで書かれているところは「文法を考えているんだな」と理解し、実際のコードが書かれているところは「実装しているんだな」と理解してください。 リンク先の文章も、最初に「BNFで、これから実装する文法を定義(というか読者へ提示)」して、次に「実際にコードを書く」という流れのようです。 >この場合だと計算パターンをすべて用意しておかなければならないという事でしょうか。例えば、string + string int + double など こちらは、その通りです。 言語を作るとき、intやfloatやstringなど、どんどん型を増やしていくと、増やしたら増やしただけ実装することが増えていきます。 また、string + string は言語によって、その機能があったりなかったりしますので、これも文法を考えるときに決定しておくべきことですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問