• Showing Page History #36002
  • Showing Page History #34446

← 先頭のページに戻る

デカルト言語のプログラム例:calc

この文書では、デカルト言語のプログラム例として式を計算する電卓のようなcalcプログラムを取り上げて説明します。

- calcプログラムは標準入力から入力された計算式を計算して結果を標準出力に結果を表示する。

- 計算式に使えるのは数値(浮動小数点数)、演算子(+-*/)、括弧()である。

- 計算式は逆ポーランド記法に変換してから、rpnf述語で実行される。


なお、calcのソースの中でインクルードするlistは、このサイトからダウンロードできるソースパッケージおよびバイナリパッケージ のexampleディレクトリの中に入っています。

例題プログラムcalcを実行するには、calcをdescartesの引数として実行してください。

  descartes calc

1. プログラムのコーディングスタイル

構文解析を行うプログラムでは、以下に示すように構文部分と操作部分を分けたコーディングスタイルで記述すると分かりやすいプログラムが書けるでしょう。


  構文部分                   操作部分



<構文ヘッド>

<構文ボディ>      <操作述語>


例)

<文>

<文1>
<文1操作・出力>
<文2>
<文2操作・出力>
;

2. calcの戦略

calcは、人間の入力する文字列で表現された計算式を構文解析して、要素ごとに分解した後に、計算しやすいように要素を組み合わせて再構成して計算を実行します。


具体的には、以下のようにデカルト言語をプログラムします。

1) 計算式を1行入力する。

2) 入力された計算式を構文解析して、逆ポーランド記法に変換する。

3) 変換された式をrpnf述語の引数として述語を合成する。

4) 合成した述語を実行する。

5) 「1) から」繰り返す

3. 構文の定義

calcの構文をEBNF(拡張バッカス記法)で記述すると以下のようになります。


EBNF(拡張バッカス記法)による構文

expr     = expradd
expradd   = exprmul { "+" exprmul | "-" exprmul }
exprmul   = exprID { "*" exprID | "/" exprID }
exprID    = "+" exprterm | "-" exprterm | exprterm
exprterm  = "(" expr ")" | 数字列


EBNF(拡張バッカス記法)の構文を、デカルト言語によって、以下のように書き直しましょう。ほぼ一対一に対応して変換できることがわかるでしょうか?


デカルト言語による構文

<expr>      <expradd>;
<expradd>    <exprmul> { "+" <exprmul> | "-" <exprmul> };
<exprmul>    <exprID> { "*" <exprID> | "/" <exprID> };
<exprID>     "+" <exprterm> | "-" <exprterm> | <exprterm> ;
<exprterm>    "(" <expr> ")" | <FNUM #t> ;


この構文では、演算子の優先度は以下の順になります。


高い

( )
単項+, 単項-
* , /
+ , -

低い

4. 行入力

キーボードより1行入力するためにはsysモジュールのgetline述語を使います。


::sys <getline #line 呼出述語>


#lineにキーボードより入力した行の文字列が設定され、それを呼出述語の入力ファイルとして設定して、オープンしてから呼び出します。

前章で作成した<expr>述語を呼び出す場合は以下のように記述します。


::sys <getline #line <expr>>

#lineに入力された文字列が設定され、それをターゲットにして<expr>述語が実行されます。

5. グローバル変数

途中の演算結果を保存するのにグローバル変数を使用します。

グローバル変数に値を設定するには、setVar述語を使います。


<setVar 変数名 設定値>


グローバル変数から値を取り出すには以下のようにします。


<変数名 #変数>


グローバル変数名の値が、#変数に設定されます。

6. ライブラリappend述語の呼び出し

::list <append 連結リスト変数 リスト1 リスト2>


append述語は、リスト1とリスト2を連結して、連結リスト変数に連結した結果のリストが設定されます。


calcのソースおよびcalcの中でインクルードするlistのファイルは、このサイトからダウンロードできるソースパッケージおよびバイナリパッケージ のexampleディレクトリの中に入っています。

calcとlistのファイルを同じディレクトリに格納して実行してください。

7. 逆ポーランド記法演算

逆ポーランド記法とは数式の記述方式の一つです。演算子を対象となる数の後に置く後置記法であり、通常の数式に比べて演算子の優先度や括弧による演算順序の乱れがなく、すっきりと前の項目から計算することができるため、演算の効率が高い方式です。

<rpn 変数 逆ポーランド式>

<rpnf 変数 逆ポーランド式>


逆ポーランド式を計算して、変数に結果を設定します。

rpnは、整数の計算を行い、rpnfは浮動小数点数の計算を行います。

8. calcの完成ソース

?<include list>; // listライブラリの読み込み


<calc #result>

<print "calc : "> // プロンプトの表示
// 1行入力して構文解析<expr>を実行する
// 結果はグローバル変数exprlistに設定される
::sys <getline #line
<setVar exprlist ()> // グローバル変数の初期化
<expr>
>
// 結果をexprlistから取り出してrpnfで演算し結果を表示する
<exprlist #x>
<rpnf #result #x>
<print " = " #result>
;

<expr>

<expradd>
;

<expradd>

<exprmul>
{
"+" <exprmul> // +演算子と合致した場合
// exprlistに+を追加する
<exprlist #x>
::list <append #list #x ("+")>
<setVar exprlist #list>
|
"-" <exprmul> // -演算子と合致した場合
// exprlistに-を追加する
<exprlist #x>
::list <append #list #x ("-")>
<setVar exprlist #list>
}
;


<exprmul>

<exprID>
{
"*" <exprID> // *演算子と合致した場合
// exprlistに*を追加する
<exprlist #x>
::list <append #list #x ("*")>
<setVar exprlist #list>
|
"/" <exprID>// /演算子と合致した場合
// exprlistに/を追加する
<exprlist #x>
::list <append #list #x ("/")>
<setVar exprlist #list>
}
;


<exprID>

"+" <exprterm>   // 単項の+演算子と合致した場合
|
"-" <exprterm>   // 単項の-演算子と合致した場合
<exprlist #x>
::list <append #list #x ("-1" "*")>
<setVar exprlist #list>
|
<exprterm>
;

<exprterm>

"(" <expr> ")" // 括弧で囲まれている場合
|
<FNUM #t>
<exprlist #x>
::list <append #list #x (#t)>
<setVar exprlist #list>
;


?{{ <calc #line> }};       // 全体を実行する。{}で2重に囲むのは、エラーで失敗しても終了させないた

め。


実行例:


$ descartes calc

calc :

10+20*30/(2+1)

= 210

calc :