<?xml version="1.0" encoding="shift_jis"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta name="generator" content="HTML Tidy for Windows (vers 1st September 2004), see www.w3.org" />

  <title>作れる！数式評価プログラム</title>
<style type="text/css">
/*<![CDATA[*/
 body {
  background-color: #88FFFF;
 }
 div.c3 {text-align: center}
 h4.c2 {text-align: right}
 h1.c1 {text-align: center}
/*]]>*/
</style>
</head>

<body>
  <h1 class="c1">作れる！数式評価プログラム</h1>

  <h4 class="c2">2002/06/25　さ〜（HTML 化　nukina）</h4>

  <h2><u>１．はじめに</u></h2>　こんにちは、佐波です。

  <p>　皆さんはプログラムを作るときに分からないことがあったらどうしますか？一番手っ取り早い解決法は近くの人に聞くことです。それでは聞いてみましょう。「○○く〜ん、今、暇？」「なんですか？」「ちょーっとプログラム手伝って欲しいんだけどー」・・・失敗です。</p>

  <p>　それではプログラム関係の掲示板かメーリングリストに聞いてみましょう。メールの Subject は「はじめまして」か「わかりません」です（注１）。肝心の内容は・・・「数式を計算するプログラムの作り方がわかりません」程度にして返事を待ちましょう。おっと、返事が来ました。なになに？「それだけでは何を作りたいかがわかりません」・・・失敗です。</p>

  <p>　これではプログラムが完成しません。そんなときには・・・おしえて、google さん！今回の場合なら「数式」「解析」「プログラム」あたりで検索をかけるとそれっぽいプログラムを見つけることができました。</p>

  <p>　というわけでインターネット上の情報は有用なのですが、求めているプログラムがそのままの形で公開されていることは非常に非常に非常に稀です。これが意味するところは、ありあわせで我慢するか、自分で作るか、です。数式を評価する程度のプログラムは、基本的な知識とそれを応用する気構えさえあれば難しいことではないので、作ってしまいましょう。</p>

  <p>　ここでは、数式を評価するプログラムの作成に際し、必要な基本的な知識と、それをどのように適用するかを見ていきます。</p>

  <h2><u>２．ＢＮＦ</u></h2>ＢＮＦは Backus Naur Form の略で、構文を定義する際に用いられる表記法です。ＢＮＦ自体は単純で、定義とその内容を次のように記述していきます。

  <p>＜定義＞：：＝＜内容＞</p>

  <p>　例えば、</p>

  <p>＜我輩＞：：＝＜猫＞</p>

  <p>という記述は、「我輩は猫である」を意味します。ここで、猫が箱の中に入っているとしましょう。そうです、アレです、シュレディンガーの猫です。この猫は箱を開けてみるまで生きている状態か、死んでいる状態かがわかりません。この場合、箱の中の猫は、二者のどちらかであることを示す「｜」を使って</p>

  <p>＜猫＞：：＝＜生きている猫＞｜＜死んでいる猫＞</p>

  <p>と定義することができます。</p>

  <p>　さて、箱の中には猫が入っているわけですが、猫が何匹かわからないとしましょう。この場合、０回以上の繰り返しを表す「<sup>*</sup>」を使って次のように定義します。</p>

  <p>＜箱の中身＞：：＝＜猫＞<sup>*</sup></p>

  <p>ただし、「<sup>*</sup>」は０個以上なので、このままでは箱の中に猫がいないことも有り得ます。少なくとも１匹は、という場合は次のようになります。</p>

  <p>＜箱の中身＞：：＝＜猫＞＜猫＞<sup>*</sup></p>

  <p>　これが１匹２匹ならたいしたことはありませんが、４匹、５匹と増えるほどに名前を付けるのが大変になってきます。んで、どうなるかと言えば・・・</p>

  <p>＜猫の兄弟＞：：＝＜太郎＞＜次郎＞＜三郎＞＜四郎＞＜五郎＞</p>

  <p>このとき注意しなければならないのは、最初が太郎で、二番目が次郎なことです。逆にはなりません。猫にもこんな名前をつけるかは問題にしないでください。</p>

  <p>　名前を付けるのに困るほどなら、オスメスあわせてたくさん、です。オスたくさんか、メスたくさん、なら</p>

  <p>＜猫たくさん＞：：＝＜オス猫＞<sup>*</sup>｜＜メス猫＞<sup>*</sup></p>

  <p>ですが、オスとメスをいっしょくたに扱うために「｛ ｝」を使います。</p>

  <p>＜猫たくさん＞：：＝｛＜オス猫＞｜＜メス猫＞｝<sup>*</sup></p>

  <p>これでは、猫がいなくても猫たくさんになりますが、まぁいいです。</p>

  <p>　ところで箱の中身ですが、箱を開けたらすぐに猫がいるとは限りません。もしかしたら、箱の中には箱があって、またその中に箱が・・・という入れ子になっていることも考えられます。</p>

  <p>＜箱の中身＞：：＝＜箱＞｜＜猫＞</p>

  <p>箱の中身には箱か猫が入っているわけですが、箱の場合はもう一回箱を開けてみることになります。そのとき、また箱が入っていればまた箱を開けて・・・何回繰り返すか知りませんが、最終的には猫が入っているのです。</p>

  <h2><u>３．数式のＢＮＦ定義</u></h2>箱の中の猫をＢＮＦで表すのに比べれば、数式をＢＮＦで表すことは簡単です。無理がないだけに話を理解するのも簡単です。

  <p>　数式には四則演算「＋−×÷」と括弧「（ ）」を含めることができるとします。評価する順序が等しいものは、「＋」と「−」、「×」と「÷」ですね。</p>

  <p>　足し算「＋」で結ばれた二つの数から成る数式は次のようになります。</p>

  <p>＜数式１＞：：＝＜数＞＋＜数＞</p>

  <p>　では、演算子「＋−」のどちらかで結ばれた二つの数から成る数式はどうでしょう。</p>

  <p>＜数式２＞：：＝＜数＞｛＋｜−｝＜数＞</p>

  <p>こうです。それでは、演算子「＋−」だけを使える数式はと言うと・・・</p>

  <p>＜数式３＞：：＝＜数＞｛｛＋｜−｝＜数＞｝<sup>*</sup></p>

  <p>　同様に、演算子「×÷」だけを使える数式について考えてみると・・・</p>

  <p>＜数式４＞：：＝＜数＞｛｛×｜÷｝＜数＞｝<sup>*</sup></p>

  <p>　最後に、括弧「（）」はと言えば、これは入れ子の中の箱と一緒です。</p>

  <p>＜数式５＞：：＝（＜数式６＞）｜＜数式７＞</p>

  <p>このとき、数式６は括弧で囲まれた式でも構いませんから、実は</p>

  <p>＜数式５＞：：＝（＜数式５＞）｜＜数式７＞</p>

  <p>です。括弧が何重になっていても、最終的には括弧で囲まれない数式７があるのです。</p>

  <p>　ここまでをまとめて、番号を振りなおします。</p>

  <p>演算子「＋−」 ＜数式１＞：：＝＜数＞｛｛＋｜−｝＜数＞｝<sup>*</sup></p>

  <p>演算子「×÷」 ＜数式２＞：：＝＜数＞｛｛×｜÷｝＜数＞｝<sup>*</sup></p>

  <p>括弧「（）」 ＜数式３＞：：＝（＜数式３＞）｜＜数式４＞</p>

  <p>　数式を評価するため、評価順序を考慮してこれらのＢＮＦを結合します。まず、数式１と数式２について、四則演算「＋−×÷」を含む次の数式を考えます。</p>

  <p>１×２＋３÷４−５</p>

  <p>この数式に対して、数式１と２のＢＮＦを当てはめてみます（図１）。</p>

  <div class="c3">
    <img src="bnf-01.jpg" />

    <p>図１．「＋−×÷」を含む数式と対応するＢＮＦ</p>
  </div>

  <p>数式１の＜数＞にあたる項は、単項の「５」を含め、数式２で表せることに注目してください。このことから、数式１のＢＮＦは次のように書き直されます。</p>

  <p>＜数式１＞：：＝＜数式２＞｛｛＋｜−｝＜数式２＞｝<sup>*</sup></p>

  <p>　次に、括弧「（ ）」も含む数式</p>

  <p>（１×２＋３）×４</p>

  <p>を考え、数式２、３のＢＮＦを当てはめます（図２）。</p>

  <div class="c3">
    <img src="bnf-02.jpg" />

    <p>図２．「＋−×÷（ ）」を含む数式と対応するＢＮＦ</p>
  </div>

  <p>数式２の＜数＞に当たる項を数式３で表すために、</p>

  <p>＜数式２＞：：＝＜数式３＞｛｛×｜÷｝＜数式３＞｝<sup>*</sup></p>

  <p>＜数式３＞：：＝（＜数式３＞）｜＜数式４＞｜＜数＞</p>

  <p>と書き改めます。さて、括弧内の数式</p>

  <p>１×２＋３</p>

  <p>は、数式１の形です。これを考慮すれば、数式３の定義は次のようになります。</p>

  <p>＜数式３＞：：＝（＜数式３＞）｜＜数式１＞｜＜数＞</p>

  <p>ちょっと待ってください。数式１は数式２→数式３へと戻ってくることを考えると、</p>

  <p>＜数式３＞：：＝（＜数式１＞）｜＜数＞</p>

  <p>これで十分です。</p>

  <p>　これで、数式の構造に関するＢＮＦでの定義は終わりました。最後に数の定義をしておきます。</p>

  <p>＜数＞：：＝｛０｜１｜２｜３｜４｜５｜６｜７｜８｜９｝<sup>*</sup></p>

  <p>最低限、１文字は欲しいところですが、簡単のためにこれでいきます。</p>

  <p>　四則演算「＋−×÷」と括弧「（ ）」を含む数式を定義するＢＮＦをまとめると、次のようになりました。</p>

  <p>＜数式１＞：：＝＜数式２＞｛｛＋｜−｝＜数式２＞｝<sup>*</sup></p>

  <p>＜数式２＞：：＝＜数式３＞｛｛×｜÷｝＜数式３＞｝<sup>*</sup></p>

  <p>＜数式３＞：：＝（＜数式１＞）｜＜数＞</p>

  <p>＜数＞：：＝｛０｜１｜２｜３｜４｜５｜６｜７｜８｜９｝<sup>*</sup></p>

  <h2><u>４．ＢＮＦに基づいたプログラム化</u></h2>　数式を定義するＢＮＦが書けたので、これに基づいて評価するプログラムを作成します。呼び出す側は次にようなプログラムにしておきます（リスト１）。

  <p>　</p>
  <hr />
  <pre>
#include &lt;stdio.h&gt;
#include &lt;ctype.h&gt;            ←isdigit（文字判定ルーチン）使用のため
char expr[] = "(1+2*3)*4/5";
void main(void) {
    printf("%s = %d\n", expr, eval( ));
 }
</pre>
  <hr />
  リスト１．数式を評価する関数を使うプログラム例

  <p>　評価する関数 eval は、評価プログラムを初期化するだけで、実際の評価は数式１の評価に丸投げします（リスト２）。初期化する変数 index は、現在見ている文字が何文字目かを表します。</p>
  <hr />
  <pre>
int index;
int eval(void) {
    index = 0;
    return expr1( );
 }
</pre>
  <hr />

  <p>リスト２．数式評価関数の導入部</p>

  <p>　次に、数式１のＢＮＦに従って数式を評価します（リスト３）。</p>
  <hr />
  <pre>
int eval1(void) {
    int value = eval2( );           ←A
    while(1) {              ←B
        if(expr[index] == '+') {        ←C
            index ++;
            value += eval2( );  ←D
         }
        else if(expr[index] == '-') {   ←C
            index ++;
            value -= eval2( );  ←D
         }
        else    break;          ←C
     }                  ←B
    return value;
 }
</pre>
  <hr />
  リスト３．数式１の評価関数 eval1

  <p>ＢＮＦとの対応を見ていくことにします（図３）。</p>

  <div class="c3">
    <img src="bnf-03.jpg" />

    <p>図３．数式１の評価プログラムとＢＮＦの対応</p>
  </div>

  <p>　まず、先頭には数式２があるので、これを評価した値を取っておきます（A）。 残りの部分は何回繰り返されるか不明なので while ループで対応します（B）。 繰り返しの先頭は必ず「＋」か「−」で（C）、そうでない場合は評価は終了です（Break 文）。 演算子「＋−」があった場合は、その後に数式２が現れるので、これを評価（D）し、 演算子に応じて加算・減算します。 　丸投げされた下請け（eval2 関数）では数式２のＢＮＦに従って評価していきます（リスト４）。</p>
  <hr />
  <pre>
int eval2(void) {
    int value = eval3( );           ←A
    while(1) {              ←B
        if(expr[index] == '*') {        ←C
            index ++;
            value *= eval3( );  ←D
         }
        else if(expr[index] == '/') {   ←C
            index ++;
            value /= eval3( );  ←D
         }
        else    break;          ←C
     }                  ←B
    return value;
 }
</pre>
  <hr />
  リスト４．数式２の評価関数 eval2

  <p>数式２のＢＮＦは数式１と同じ形をしているのでプログラムも同じになります（注２）。 ＢＮＦとの対応も同様になる（図４）ので詳説は省略します。</p>

  <div class="c3">
    <img src="bnf-04.jpg" />

    <p>図４．数式２の評価プログラムとＢＮＦの対応</p>
  </div>

  <p>　次に数式３の評価関数 eval3 です（リスト５）。</p>
  <hr />
  <pre>
if(expr[index] == '(') {            ←A
        index ++;
        int value = eval1( ); int eval3(void) {  ←B
        if(expr[index] != ')')      ←C
            puts("')'が閉じていない。");
        index ++;
        return value;
     }
    else    return number( );       ←D
 }
</pre>
  <hr />
  リスト５．数式３の評価関数 eval3

  <p>評価関数 eval3 とＢＮＦの対応を見ていきます（図５）。</p>

  <div class="c3">
    <img src="buf-05.jpg" />

    <p>図５．数式３の評価プログラムとＢＮＦの対応</p>
  </div>

  <p>まず最初の１文字が左括弧「（」であれば（A）、ＢＮＦに従って括弧内を数式１として評価します（B）。 右括弧「）」のチェック（C）が申し訳程度にできます。 最初の１文字が左括弧「（」でない場合は、数であるとみなして評価します（D）。</p>

  <p>　最後に数を評価する関数 number です（リスト６）。</p>
  <hr />
  <pre>
int number(void) {
    int value = 0;
    while(isdigit(expr[index])) {
        value = value*10 + (expr[index] - '0');
        index ++;
     }
    return value;
 }
</pre>
  <hr />
  リスト６．数の評価関数 number

  <p>順番に１文字ずつ見ていき、文字が数字だったら今までに読んだ数を 10 倍し、 新しい文字に対応する数字を加えるという、極めて単純な関数です。</p>

  <p>　以上のリストを併せると数式評価のプログラムは完成です（<a href="expr.cpp">expr.cpp</a>）。</p>

  <h2><u>５．おわりに</u></h2>

  <p>今回は四則演算「＋−×÷」と括弧「（ ）」だけの数式でしたが、 ＢＮＦを起こすことができるようになれば各種演算子を簡単に加えることができます。 整数で演算していることを利用してビット演算を加えたり、 浮動小数点表現を使って計算の精度をあげるということもできます。</p>

  <p>　今回のプログラムでは説明の流れ上、エラー処理を大幅に省略しています。 使う場合には、エラーを見つけたときにどうするかを含め、悩みはつきません。</p>

  <p>　yacc や lex を使っての構文解析が有名ですが、手作業で作成するのもプログラムの楽しみです。 簡単な構文程度ならそんなに時間がかかることもありませんしね。 あ、今回一番時間がかかっているのは当然ながらこの文章ということで。</p>

  <h2><u>参考</u></h2>宮田高志さんのページ（<a href="http://cl.aist-nara.ac.jp/staff/takashi/home.html">http://cl.aist-nara.ac.jp/staff/takashi/home.html</a>）内

  <p>　　再帰下降構文解析：<a href="http://cl.aist-nara.ac.jp/staff/takashi/rec-desc/index.html">http://cl.aist-nara.ac.jp/staff/takashi/rec-desc/index.html</a></p>

  <p>C MAGAZINE 2000年5月号：特集１ スクリプト言語を作ろう 水野順</p>

  <p>　　内容概要：<a href="http://www.cmagazine.jp/contents/200005.html">http://www.cmagazine.jp/contents/200005.html</a></p>

  <p><br />
  注１）メールの Subject は「はじめまして」か「わかりません」です</p>

  <p>絶対に真似してはいけません。</p>

  <p>注２）数式２のＢＮＦは数式１と同じ形をしているのでプログラムも同じになります</p>

  <p>除算の除数が０の場合の対応はすべきです。</p>
</body>
</html>

