出題日 | : | 2001/06/29 |
提出期限 | : | 2001/08/15 |
提出日 | : | 2001/08/15 |
TNCの特徴や機能は次の通りである。
TNCはVisual C++ Ver.6.0でもビルドすることが可能である。 ただし、環境によってはsource/ include/common.hppにおけるマクロの定義を変更することが必要である。
シンボルは、C言語などの識別子と同じである。 さらに、```''と``'''に括られた文字の列もシンボルとなる。 実数は、C言語などの整数定数と浮動小数点定数を併せたものにほぼ一致する。 虚数は、実数の直後にサフィックスとして``i''を付加したものである。 文字列は、``"''で括られた文字の列である。 予約語は、次のものである。
nil, true, if, then, else, while, lambda, try, catch, local記号は、``('', ``)''等といったものである(正確にはsource/lex.lを参照)。 なお、シングルクオート、ダブルクオートの中では、Javaライクなエスケープシーケンスを使うことができる。
字句解析で扱うものとしては、この他に、コメントとエスケープされた改行、空白類がある。 コメントはC言語のように``/*''で始まって``*/''までのものと、シェルスクリプトのように``#''で 始まって改行までのものの、二通りが使える。 エスケープされた改行は、``\''の直後に改行が来ているもので、この改行は無視される。 空白類は、スペース、水平タブ、キャリッジリターン、垂直タブ、フォームフィードであり、これらは無視される。
source/include/common.hppでUNICODEというマクロが定義されていると、入出力はUnicode文字を使って行われる。 この時、flexの生成する字句解析器はシングルバイト文字しか処理できないので、一旦ユニコードエスケープ(\uXXXX)に変換して 字句解析器を通し、再びユニコード文字に戻している。 従って、入出力がUnicodeでも、ASCII(7bit)文字以外の文字は、クオートの中でしか使えないことになる。
各構文要素の解析の結果はセルとして得られる。 最終的に、expression-input の解析結果のセルが、構文解析の結果となる。
expression-input は文法の開始記号である。 これが、一行の入力、或いは、ファイル全体となる。 END-INPUT は、インタラクティブな入力ならば改行記号とEOF、非インタラクティブな入力ならばEOFのみとなる。
primary-expression の解析結果は、次の通りである。 nil ならばNilセル、true ならばTrueセル、symbol ならばそのシンボルを持つシンボルセル、 real-constant ならばその数を持つ実数データセル、imaginary-constant ならばその数を持つ複素数データセル、 string-constant ならばその文字列を持つ文字列セルとなる。 その他の場合は、下位の解析結果がそのまま解析結果となる。
parenthesis-expression の解析結果は、expression の解析結果となる。
expression-list :
expression
expression-list , expression
list-expression の解析結果は、各expression を要素にもつリストの先頭のコンスセルとなる。
two-dimensional-expression-list :
two-dimensional-expression-list-sub
two-dimensional-expression-list ; two-dimensional-expression-list-sub
two-dimensional-expression-list-sub :
assignment-expression
two-dimensional-expression-list-sub , assignment-expression
matrix-expression の解析結果は、``[]''を関数記号とし、two-dimensional-expression-list の表す 二次元的なリストを引数とする構造セルとなる。
if-expression の解析結果は、関数記号を``if''とし、引数を二つ或いは三つの下位のセルとする構造セルである。 ただし、二番目と三番目のセルは、引用セルで括られる。
while-expression の解析結果は、関数記号を``while''とし、引数を三つの下位のセルとする構造セルである。 ただし、二番目と三番目のセルは、引用セルで括られる。
lambda-expression の解析結果は、関数記号を``lambda''とし、第一引数をlist-expression の解析結果、 第二引数を最後のparenthesis-expression の解析結果、そして一つ目のparenthesis-expression が省略されなかった場合は その結果を第三引数とする構造セルである。 ただし、全ての下位セルは、引用セルで括られる。
try-expression の解析結果の構造セルは、関数記号を``try''とし、第一引数を一つ目の parenthesis-expression の解析結果とし、二つ目のparenthesis-expression が記された場合は 第二引数をその結果とする。 ただし、一つ目のparenthesis-expression の結果は、引用セルで括られる。
local-expression の解析結果の構造セルは、関数記号として``local''を持ち、第二引数としてlist-expression の結果を、第三引数としてparenthesis-expression の結果を持つ。 ただし、第二、第三引数は引用セルで括られる。
postfix-operator : one of
' .'
postfix-expression の解析結果は、次の通りである。
``primary-expression ''の場合は、その結果が解析結果となる。
``postfix-expression ( expression-list ) ''の場合は、関数記号をpostfix-expression の結果、 引数をexpression-list の各要素とする構造セルとなる。
``postfix-expression . primary-expression ''の場合は、関数記号を``.''とし、引数を postfix-expression の結果とprimary-expression の結果とする構造セルとなる。 ただし、第二引数は引用セルで括られる。
``postfix-expression postfix-operator ''の場合は、関数記号をpostfix-operator の結果とし、 引数をpostfix-expression とする構造セルとなる。
unary-expression の解析結果は、次の通りである。 一番目の場合は、postfix-expression の結果が解析結果となる。 二番目の場合は、関数記号としてunary-operator の結果を持ち、引数としてunary-expression の結果を持つ構造セルとなる。 三番目の場合は、unary-expression の結果を内容として持つ引用セルとなる。
power-operator : one of
'136 . '136 ** .**
power-expression の解析結果は、次の通りである。 一番目の場合は、unary-expression の結果が解析結果となる。 二番目の場合は、関数記号としてpower-operator の結果を持ち、引数として unary-expression の結果とpower-expression の結果を持つ構造セルとなる。
multicative-operator : one of
* / % .* ./ .% .
multicative-expression の解析結果は、次の通りである。 一番目の場合は、power-expression の結果が解析結果となる。 二番目の場合は、関数記号としてmulticative-operator の結果を持ち、引数として multicative-expression の結果とpower-expression の結果を持つ構造セルとなる。
additive-operator : one of
+ - .+ .-
additive-expression の解析結果は、次の通りである。 一番目の場合は、multicative-expression の結果が解析結果となる。 二番目の場合は、関数記号としてadditive-operator の結果を持ち、引数として additive-expression の結果とmulticative-expression の結果を持つ構造セルとなる。
shift-operator : one of
<< >> <<< >>>
shift-expression の解析結果は、次の通りである。 一番目の場合は、additive-expression の結果が解析結果となる。 二番目の場合は、関数記号としてshift-operator の結果を持ち、引数として shift-expression の結果とadditive-expression の結果を持つ構造セルとなる。
vector-creation-expression の解析結果は、次の通りである。 一番目の場合は、shift-expression の結果が解析結果となる。 二番目の場合は、関数記号として``:''を表すシンボルセルを持ち、引数として 二つのshift-expression の結果を持つ構造セルとなる。 三番目の場合は、関数記号として``:''を表すシンボルセルを持ち、引数として 三つのshift-expression の結果を持つ構造セルとなる。
relational-operator : one of
< > <= >=
relational-expression の解析結果は、次の通りである。 一番目の場合は、vector-creation-expression の結果が解析結果となる。 二番目の場合は、関数記号としてrelational-operator の結果を持ち、引数として relational-expression の結果とvector-creation-expression の結果を持つ構造セルとなる。
equality-operator : one of
!= '176= <>
equality-expression の解析結果は、次の通りである。 一番目の場合は、relational-expression の結果が解析結果となる。 二番目の場合は、関数記号としてequality-operator の結果を持ち、引数として equality-expression の結果とrelational-expression の結果を持つ構造セルとなる。
logical-and-expression の解析結果は、次の通りである。 一番目の場合は、equality-expression の結果が解析結果となる。 二番目の場合は、関数記号として``&&''を表すシンボルセルを持ち、引数として logical-and-expression の結果とequality-expression の結果を持つ構造セルとなる。 ただし、第二引数は引用セルで括られる。
logical-or-expression の解析結果は、次の通りである。 一番目の場合は、logical-and-expression の結果が解析結果となる。 二番目の場合は、関数記号として``||''を表すシンボルセルを持ち、引数として logical-or-expression の結果とlogical-and-expression の結果を持つ構造セルとなる。 ただし、第二引数は引用セルで括られる。
assignment-operator : one of
'136= . '136= **= .**=
*= .*= /= ./= %= .%= .
+= .+= -= .-=
<<= >>= <<<= >>>=
assignment-expression の解析結果は、次の通りである。 一番目の場合は、logical-or-expression の結果が解析結果となる。 二番目の場合は、関数記号としてassignment-operator の結果を持ち、引数として unary-expression の結果とassignment-expression の結果を持つ構造セルとなる。
compound-expression の解析結果は、次の通りである。 一番目の場合は、assignment-expression の結果が解析結果となる。 二番目の場合は、関数記号として``;''を表すシンボルセルを持ち、引数として compound-expression の結果を持つ構造セルとなる。 三番目の場合は、関数記号として``;''を表すシンボルセルを持ち、引数として compound-expression の結果とassignment-expression の結果を持つ構造セルとなる。
expression の解析結果は、compound-expression の結果である。
|
以下の表記では、例えば
``()''( Function f, Cons args )ならば、第一引数に関数セルを受け取り、第二引数にコンスセルを受け取る関数セルが``()''というシンボルセルにバインドされている、 ということを表している。
``()''( Function f, Cons args )
fに対して、argsという引数で関数呼び出しを行う。 戻り値は、この関数呼び出しの戻り値とする。
``lambda''( Cons params, Cell body )
``lambda''( Cons params, Cell body, Cell tail )
引数仕様をparamsとし、本体をbodyとする関数セルを作成し、それを戻り値とする。 また、tailが記された場合は、引数リストをparamsとし、本体をbody、tailを末端部とする関数セルを作成する。 ここで作成された関数セルに対して関数呼び出しが行われると、次のような処理を行う。
引数仕様は、シンボルセルとクラスセルの二つからなるリストの、リストである。 シンボルセルとクラスセルの組が、その引数の型を表している。
``if''( Cell c, Cell t )
``if''( Cell c, Cell t, Cell e )
cを論理値解釈し、それが真であるならばtを評価してその結果を戻り値とする。 偽であり、かつ二引数で呼ばれたならば、nilを戻り値とする。 偽であり、かつ三引数で呼ばれたならば、eを評価してその結果を戻り値とする。
``while''( Cell cond, Cell body )
``try''( Cell body )
``try''( Cell body, Function c )
bodyを評価し、その結果を戻り値とする。 ただし、bodyの評価中に例外が投げられた場合は、次のようにする。 一引数で呼ばれた場合は、その例外の持つセルを戻り値とする。 二引数で呼ばれた場合は、その例外の持つセルを引数としてcに対して関数呼び出しを行い、その戻り値を戻り値とする。 ただし、その例外の持つセルがcの引数仕様に適合しないのならば、同じセルを伴って再び例外を投げる。
``throw''( Cell arg )
argを持つ例外を作成し、その例外を投げる。
``local''( Cons vars, Cell body )
bodyの中に出現する、varsの構成要素のシンボルセルを、新しいものに全て付け替えてからbodyを評価し、 その結果を戻り値とする。
``!''( Nil n )
``!''( True t )
一番目の場合は、trueを返す。 二番目の場合は、nilを返す。
``&&''( Nil n, Cell c )
``&&''( True t, Cell c )
一番目の場合は、nilを返す。 二番目の場合は、cを評価し、その論理値解釈を返す。
``||''( Nil n, Cell c )
``||''( True t, Cell c )
一番目の場合は、cを評価し、その論理値解釈を返す。 二番目の場合は、trueを返す。
``=''( Symbol s, Cell c )
sに対してcをバインドする。 cを戻り値とする。
``;''( Cell c1 )
``;''( Cell c1, Cell c2 )
一番目の場合は、nilを返す。 二番目の場合は、c2を返す。
また、基本的な算術関数として、``sqrt'', ``exp'', ``log'', ``log10'', ``sin'', ``sinh'', ``cos'', ``cosh'', ``tan'', ``tanh'', ``sec'', ``sech'', ``csc'', ``csch'', ``cot'', ``coth'', ``asin'', ``asinh'', ``acos'', ``acosh'', ``atan'', ``atan2'', ``atanh'', ``asec'', ``asech'', ``acsc'', ``acsch'', ``acot'', ``acoth''が複素数上で定義されている。
そして、``sign'', ``max'', ``min'', ``floor'', ``ceil'', ``round'', ``fix''も定義されている。
複素数を扱う関数として、``real'', ``imag'', ``abs'', ``angle'', ``conj''が定義されている。
比較演算子としては、``=='', ``!='', ``<'', ``>'', ``<='', ``>=''が定義されている。
行列を扱う関数として、``''', ``.''', ``size'', ``abs'', ``norminf'', ``diag'', ``\'', ``inv'', ``eig'', ``[]'', ``rand'', ``zeros'', ``ones'', ``eye''等が定義されている。 これらは、MATLABと同様の仕様である。
丸め方向の制御として、``up'', ``down'', ``near'', ``chop''が定義されている。
``fopen''( String filename, Cons options )
filenameで示されるファイル名のファイルを開く。 この時、optionsの構成要素に``read''というシンボルセルが含まれているならば読取専用として、 ``write''というシンボルセルが含まれているならば書き出し専用として、開く。 このどちらかは必ず指定しなければならない。 また、``binary''というシンボルセルが含まれているならばバイナリストリームとして開く。 戻り値は、開いたストリームのストリームセルである。
``feof''( Stream s )
sがEOFまで読まれているかどうかを判定し、読まれていた場合はtrueを、そうでない場合はnilを返す。
``fget''( InputStream s )
sから行を単位に読み取り、構文解析をし、その結果のセルを返す。
``fload''( InputStream s )
sからストリーム全多淫を単位に読み取り、構文解析をし、その結果のセルを返す。
``fprint''( String s, OutputStream str )
strに文字列sを書き出す。
``fput''( Cell c, OutputStream str )
strにcを文字列に変換して書き出す。
``precision''( OutputStream s, Real prec )
sに数値を出力する際の表示精度をprecとする。
``classof''( Cell c )
cのクラスを表すクラスセルを返す。
``instanceof''( Cell c, Class class )
cがclassのインスタンス又はclassのサブクラスのインスタンスであるならば、trueを返す。 そうでないならば、nilを返す。
``timing''( Cell c )
cを評価し、その評価にかかった時間を秒単位で返す。
``exit''()
TNCの実行を終了する。
``eval''( Cell c )
cを評価し、その結果を戻り値とする。
``tostr''( Cell c )
cを文字列に変換して、それを戻り値とする。
``overload''( Function f1, Function f2 )
f1とf2から新たな関数セルを作成し、それを戻り値とする。 新たな関数セルに対して関数呼び出しが行われると、まず、f1の引数仕様に実引数が適合するかどうかを調べ、 適合する場合は、f1を呼び出す。 そうでない場合は、f2を呼び出す。
``=''( Cons dst, Cons src )
dstとsrcのcar部分同士とcdr部分同士にそれぞれ``=''を呼ぶ。
とインタプリタ指定を書いておき、ファイルを実行可能にしておけば、自動的にTNCで実行することができる。#! ./tnc -f
$ sample.tnc ans = nil ans = nil ans = 0.0999999999999999917 -0.100000000000000006 0.0999999999999999917 ans = 0.0999999999999999917 0.199999999999999983 -0.300000000000000044 ans = nil ans = 0.100000000000000006 -0.0999999999999999917 0.100000000000000006 ans = 0.100000000000000006 0.200000000000000011 -0.299999999999999989 ans = 0.100000000000000006 -0.0999999999999999917 0.100000000000000006 ans = nil ans = 0.100000000000000006 0.200000000000000011 -0.299999999999999989 ans = nil ans = -0.0399999999999999731 ans = nil ans = -0.0400000000000000147 ans = nil
また、lin.tncは関数として、eig.tncはスクリプトとして書かれている。 従って、lin.tncを試す時は、
とする。
TNC:1> load( "lin.tnc" ) ans = function TNC:2> A=[1,2,-3; -2,3,4; 3,-4,5]; ans = nil TNC:3> b=[1; 2; -3.3]; ans = nil TNC:4> {x,e}=linear(A,b) ans = { -0.2705128205 0.5564102564 -0.05256410256 , 2.761323933e-16 2.675922162e-16 8.824849683e-17 }
そして、eig.tncを試す時は、
とする。
TNC:5> exec( "eig.tnc" ) -1.46585729663240727 -0.667606283389616806 2.34968353448908829 4.78378004553293756 8.45945518408088917e-15 7.41980936377228185e-15 9.61055457365009797e-15 1.27807331540120258e-14 ans = nil
execやloadはinit.tncの中で定義されている。 また、init.tncでは``()''をオーバーロードして行列 A の( r, c )成分(0オリジン)を
で得られるようにしている。A( r, c )
なお、実際に実行すると、execやload等で``end of file''という例外が発生するが、これは現在の仕様であり特に問題はない。 feofは、EOFまで読みこんだストリームに対してtrueを返すので、EOFの直前まで読みこんだ状態ではtrueが返ってこないためである。
それに対して、数値計算部分は問題点が多い。 例えば、eigで得られる固有値の対角行列は、現在のところ普通のフル行列のデータ構造に入っている。 これは、時間的にも空間的にも無駄が多い。 また、LU分解を扱うようになると、置換行列を表すデータ構造も必要になるであろう。 このような、数値計算部分に関する問題は、主に解決すべきことの多さによるので、今回は時間との兼ね合いから 適当なところで打ち切ることにした。
時間との兼ね合いということでは、オブジェクトセルの実装も実現することができなかった。 関数のオーバーロードは既に可能であるので、後は、ユーザ定義クラスが作成できれば、それなりの オブジェクト指向プログラミングも可能となる。 ユーザ定義クラスで区間クラスなども実装してみたかったので、この点が最も残念な点である。
ところで、この課題を行うに当たって、octaveやMATLAB等の仕様を調べてみたが、プログラミング言語として見ると、 これらの数値計算ツールで使われている言語は、必ずしもモダンではないように感じる。 例えば、演算子の前後に空白を入れるだけで意味が変わる構文がある点である([1 -2;2 -1]と[1-2;2-1]など)。 或いは、``'''という文字が共役転置の記号と、シングルクオートの記号の両方に使われているため、 字句解析の段階で文脈に応じて判断している点もそうである。 このような直交性についてだけ言えば、TNCはoctaveやMATLABに勝っている可能性がある。
cygwinの環境は、必ずしも完全ではないようである。 即ち、sscanfはどうやらまともに解析をしないようなのである。 特に顕著なのが、浮動小数点数を読みこもうとすると、小数点を読み飛ばした数に解析される点である。 そこでTNCでは、C++のstd::istringstreamを使うことでこの問題を回避した。 同様の問題は、以前、cygwin上にoctaveをインストールしようとした時にも発生したので、調べてみると、 octaveの字句解析でも浮動小数点数の解析にsscanfが使われていることが分かった(octave 2.0.16ではsrc/lex.l(1420))。 従って、ここをstd::istringstreamを使う方法に変えれば、cygwin上にもoctaveをインストールできるのではないだろうか。