プログラムな日常>ウェブプログラム作品集>
式の評価
(evaluation)

source
interpretation
redefine
result by redefinition




使い方:

  1. source のテキストボックスに、変数定義と式とを入れる。変数定義は","で区切り、変数定義と式とは";"で区切る。
  2. interpret をクリックすると、interpretation のテキストボックスに計算過程と結果とが表示される。
  3. redefine のテキストボックスで、変数値を再定義する。
  4. exec をクリックすると、再定義に基づく計算がなされる。


解説:

コンパイラやインタープリターなど、プログラムを解釈する機能を作りたいとはずっと思っていました。 今回は代入文の右辺などの式を解釈し、数値化するものを作ってみました。 式の解釈は、数式処理などで試みたことがあり、括弧が多重についた式に対処するため、樹構造や、スタックのような構造も試してみました。 しかし、複雑な構造は、見通しが悪く、デバッグがやりづらい点が問題でした。 今回作ってみたものでは、普通の式の順序のまま、各ステップを一行の文字列に変換して見通せます。 配列の要素は文字列と数値のどちらかであり、空白は含みません。 一行の文字列に変換するとき、配列の境目には空白を入れているので、空白があればそれが要素の境目とわかります。
式の解釈(と計算)は、文字列であるもとの式を配列に変換する前処理、配列にした式から1個の数値を求める解釈と(同時の)コンパイル、変数が変更された場合の再計算から成ります。


【式の仕様と前処理】

評価すべき式の仕様として、積についてはプログラミング言語のような書き方ではなく、数学の記法に近いものを許し、"*"、"·" および"×"の他に空白" "が可能です。 例えば、"a b" (間に空白を入れています)はaとbの積として許容します。さらに、"a 2" も積としてOKです。 空白を入れない"ab"や"a2"は積ではなく、それぞれ単一の変数になる一方、数字の次に変数が来る場合に限って、間に空白がなくても積と見做し、"2a" は2とaの積になります。 名前には数字の他に"_"も、また、先頭以外では"."も使えます。
前処理においては、これを名前および数値の配列に作りますが、プログラム実行時に判断し易いように、作業用の中間データでは積には必ず "*" を用いています。 もとの式をこのように改変した配列をlistの初期状態とします。
なお関数はjavascript の Math. で始まる関数をそのまま書くようにしました。Math. の定数も利用できます。
求めるのは式の値ですが、"="を1個含んだ式も可能であり、この場合、 左辺-(右辺) の値が求められます。
これは Tiles の次期 version を狙った仕様です。


【解釈とコンパイル、再計算】

前処理で作られたlistを変形して、答えを作って行きます。 listの上の位置を示すポインタlptrが、現在着目する配列上の位置を示します。
計算は次のループを回すことによって行われます。

  1. 現在の配列の内容を審査し、次の処理をどうすべきか判断し、「予告」する。この予告は12種類あり、 <→><a→><^><*></><+><0+><-><0-><()><f><.>と名付けられる。 審査されるのは、lptr より前の区間の最後に位置する"("のせいぜい直前から、lptr が指している(すぐ次の)要素までである。
  2. 有意な変更(定義は後述)があった、または予定される場合には、テキストボックスの内容に現在の状態を追加。 この「現在の状態」とは、まずlistの内容を、要素の間に空白を入れながら表示。 着目するlptrの位置をカーソル"█"で表示。 次にlistの内容を、要素の間に空白を入れながら表示。最後に、現在の状態をもとに次の変形をどうするかの「予告」を表示。以上で一行となる。 ただし、この一行の「追加」は、単にlptrを1つ増やし、内容に変更がない箇所では省略される(変更の前後で表示される)。
  3. もしlistの内容が単一の数値であれば、ここで終了し、ループを抜ける。
  4. 「予告」された処理を行う。ただし、初期状態として予告された処理は、lptrの単純な増加<→>である。

各行の表示は次のように行われます。

  
ステップ <-- list(lptr 以前) -->カーソル<------------ list(lptr 以降) ------------>処理予告

以上がインタープリターの動作ですが、ここで「処理予告」は、有意なもののみ記憶され、パラメータを変えた再計算時に役立てるので、コンパイラとも言えます。
ここで、普通の代入式解釈では、配列の内容を「審査」したら直ちにそれを実行するはず。 しかし、それだと、どういう状態で処理の判断がなされたかが見えないので、処理の「予告」と、実際の処理とを分け、その間に表示を入れるようにしました。
個々の「処理予告」は次の基準で。
「処理予告」のコードに続く第1引数は、カーソルの(左からの)位置。第2引数がある場合は、関数における引数の数。
さて、先に述べた「有意」な「処理予告」とは、最初の<→>を除く全てであって、これらは記憶され、つまりコンパイルされ、変数値を変えての実行がある場合、利用されます。

このやり方では次のような原則が守られます。
計算は原則として左から順に解決しますが、次の場合は例外として右から計算されます。 具体的な計算は次の場合です(表の上位の行が優先されます; n1, n2 は数値)。
配列上の位置lptr-pnum-1lptr-pnumlptr-3lptr-2lptr-1lptr結果(赤から黄までを置換)
配列の内容n1"^"n2"^"以外n1^n2
n1"*", "/"n2"^"以外n1*(/)n2
n1"+", "-"n2"^", "*", "/" 以外n1+(-)n2
",", "(", 式の冒頭+n1n1
",", "(", 式の冒頭-n1"^"以外-n1
"Math.*""("")"関数値
"Math.*" などでない"("n1")"n1
n1配列外終了

計算後、lptr は、計算された数値の次を指します。

押しつけがましいようですが、コードを置いときます。