ユーザ言語 Asir
**********

Asir の組み込み函数は, 因数分解, GCD などの計算を行うもの, ファイル入出力を行うもの, あるいは数式の一部を取り出すものなどさまざまなものが用意されているが, ユーザが実際に行いたいことを実行させるためには一般にはユーザ言語によるプログラムを書く必要がある. ユーザ言語も Asir と呼ばれる. 以下では, ユーザ言語の文法規則および実際のユーザ言語プログラムを例としたプログラムの書き方について述べる.

文法 (C 言語との違い)
=============

Asir の文法は C 言語に準拠している. おもな相違点は次の通りである. 以下で, 変数とは Asir におけるプログラム用の変数, すなわち大文字で始まる文字列を意味することとする.

  * 変数の型がない.

     既に説明したとおり, Asir で扱われる対象自身は全て何らかの型を持っている. しかし, プログラム変数自体は, どのような対象でも代入できるという意味で型がないのである.

          [0] A = 1;
          1
          [1] type(A);
          1
          [2] A = [1,2,3];
          [1,2,3]
          [3] type(A);
          4

  * 函数内の変数は, デフォルトでは仮引数をこめてすべて局所変数. ただし, extern 宣言された変数は, トップレベルにおける大域変数となる.

     すなわち, 変数のスコープは大域変数と局所変数の 2 種類に単純化されている. トップレベル, すなわちプロンプトに対して入力された変数は全て大域変数として登録される. また函数内では次のいずれかとなる.

      1. 函数が定義されるファイルにおいて, その函数定義以前に, ある変数が extern 宣言されている場合, 函数内のその変数も大域変数として扱われる.

      2. extern 宣言されていない変数はその函数に局所的となる.

          % cat afo
          def afo() { return A;}
          extern A$
          def bfo() { return A;}
          end$
          % asir
          [0] load("afo")$
          [5] A = 1;
          1
          [6] afo();
          0
          [7] bfo();
          1

  * プログラム変数は大文字で始まり, 不定元, 函数は小文字で始まる.

     この点は, 既存の数式処理システムのほとんどと異なる点である. Asir がこの仕様を採用したのは, ユーザが不定元のつもりで使用した変数になんらかの値が代入されていた場合に混乱を招く, という, 既存のシステムにありがちな状況を避けるためである.

  * switch 文, goto がない.

     goto がないため, 多重ループを一度に抜けるのがやや複雑になる場合がある.

  * コンマ式は, for (A;B;C) または, while(A) の A, B, C にのみ使うことができる.

     これは, リストを正式なオブジェクトとして加えたことによる.

以上は制限であるが, 拡張としては次の点が挙げられる.

  * 有理式に対する計算を, 通常の C における計算と同様にできる.

  * リストが扱える.

     構造体を用いるまでもない要素の集合体を, リストで表すことができ, C で直接書く場合に比較してプログラムが短く, 読みやすく書ける.

  * ユーザ定義函数におけるオプション指定.

     これに関しては, see オプション指定.

ユーザ定義函数の書き方
===========

ユーザ定義函数
-------

ユーザによる函数の定義は def 文で行う. 文法エラーは読み込み時にある程度チェックされ, おおよその場所が表示される. 既に(引数の個数に関係なく)同名の函数が定義されている場合には, その函数は再定義される. ctrl() 函数により verbose フラグが on になっている場合,

     afo() redefined.

というメッセージが表示される. ある函数の定義において, まだ未定義の函数を呼び出していても, 定義時にはエラーにならない. 実行時に未定義の函数を呼び出そうとした場合にエラーとなる.

     def f(X) {
         if ( !X )
             return 1;
         else
             return X * f(X-1);
     }

     def c(N)
     {
         A = newvect(N+1); A[0] = B = newvect(1); B[0] = 1;
         for ( K = 1; K <= N; K++ ) {
             A[K] = B = newvect(K+1); B[0] = B[K] = 1;
             for ( P = A[K-1], J = 1; J < K; J++ )
                 B[J] = P[J-1]+P[J];
             }
         return A;
     }

2 つ目の例では, 長さ N+1 のベクトル (Aとする) が返される. A[I] は長さ I+1 の配列であり, そのそれぞれの要素が ICJ を要素とする配列である.

以下では, C によるプログラミングの経験がない人のために, Asir 言語によるプログラムの書き方を解説する.

変数および不定元
--------

既に述べた通り, Asir においてはプログラム変数と不定元を明確に区別している.

変数
     大文字で始まり, アルファベット, 数字, _ からなる文字列

     変数あるいはプログラム変数とは, Asir のさまざまな型の内部形式を格納するための箱であり, 格納された内部形式が, この変数の値である.  変数が式の要素として評価される時は, そこに収められた値に置き換えられる. すなわち, 内部形式の中にはプログラム変数は現れない. 変数は全て 0 で初期化されている.

          [0] X^2+X+1;
          1
          [1] X=2;
          2
          [2] X^2+X+1;
          7

不定元
     小文字で始まり, アルファベット, 数字, _ からなる文字列

     不定元とは, 多項式環を構成する際に添加される変数をいう. Asir においては, 不定元は値をもたない超越的な元であり, 不定元への値の代入は許されない.

          [3] X=x;
          x
          [4] X^2+X+1;
          x^2+x+1

引数
--

     def sum(N) {
         for ( I = 1, S = 0; I <= N; I++ )
             S += I;
         return S;
     }

これは, 1 から N までの自然数の和を求める函数 sum() の定義である. この例における sum(N) の N が引数である. この例は, 1 引数函数の例であるが, 一般に引数の個数は任意であり, 必要なだけの個数を , で区切って指定することができる. 引数は値が渡される. すなわち, 引数を受けとった側が, その引数の値を変更しても, 渡した側の変数は変化しない. ただし, 例外がある. それは, ベクトル, 行列を引数に渡した場合である. この場合も, 渡された変数そのものを書き替えることは, その函数に局所的な操作であるが, 要素を書き換えた場合, それは, 呼び出し側のベクトル, 行列の要素を書き換えることになる.

     def clear_vector(M) {
         /* M is expected to be a vector */
         L = size(M)[0];
         for ( I = 0; I < L; I++ )
             M[I] = 0;
     }

この函数は, 引数のベクトルを 0 ベクトルに初期化するための函数である. また, ベクトルを引数に渡すことにより, 複数の結果を引数のベクトルに収納して返すことができる. 実際には, このような場合には, 結果をリストにして返すこともできる. 状況に応じて使いわけすることが望ましい.

コメント
----

C と同様 /* と */ で囲まれた部分はコメントとして扱われる.

     /*
      * This is a comment.
      */

     def afo(X) {

コメントは複数行に渡っても構わないが, 入れ子にすることはできない. /* がいくつあっても最初のもののみが有効となり, 最初に現れた */ でコメントは終了したと見なされる. プログラムなどで, コメントを含む可能性がある部分をコメントアウトした場合には, #if 0, #endifを使えばよい. (see プリプロセッサ)

     #if 0
     def bfo(X) {
     /* empty */
     }
     #endif

文
-

Asir のユーザ函数は,

     def 名前(引数,引数,...,引数) {
         文
         文
         ...
         文
     }

という形で定義される. このように, 文は函数の基本的構成要素であり, プログラムを書くためには, 文がどのようなものであるか知らなければならない. 最も単純な文として, 単文がある. これは,

     S = sum(N);

のように, 式に終端記号 (; または $) をつけたものである. この単文及び類似の return 文, break 文などが文の最小構成単位となる. if 文や for 文の定義 (see 文法の詳細) を見ればわかる通り, それらの本体は, 単なる一つの文として定義されている. 通常は, 本体には複数の文が書けることが必要となる.  このような場合, { と } で文の並びを括って, 一つの文として扱うことができる. これを複文と呼ぶ.

     if ( I == 0 ) {
         J = 1;
         K = 2;
         L = 3;
     }

} の後ろには終端記号は必要ない. なぜなら, { 文並び }が既に文となっていて, if 文の要請を満たしているからである.

return 文
--------

return 文は,

     return 式;

     return;

の 2 つの形式がある. いずれも函数から抜けるための文である. 前者は函数の値として 式 を返す. 後者では, 函数の値として何が返されるかはわからない.

if 文
----

if 文には

     if ( 式 )             if ( 式 )
         文       及び         文
     else
         文

の 2 種類がある. これらの動作は明らかであるが, 文の位置に if 文が来た場合に注意を要する. 次の例を考えてみよう.

     if ( 式 )
         if ( 式 ) 文
     else
         文

この場合, 字下げからは, else 以下は, 最初の if に対応するように見えるが, パーザは, 自動的に 2 番目の if に対応すると判断する. すなわち, 2 種類の if 文を許したために, 文法に曖昧性が現れ, それを解消するために, else 以下は, 最も近い if に対応するという規則が適用されるのである. 従って, この例は,

     if ( 式 ) {
         if ( 式 ) 文 else 文
     }

という意味となる. 字下げに対応させるためには,

     if ( 式 ) {
         if ( 式 ) 文
     } else
         文

としなければならない.

ループ, break, return, continue
----------------------------

ループを構成する文は, while 文, for 文, do 文の 3 種類がある.

  * while 文

     形式は,

          while ( 式 ) 文

     で, これは, 式 を評価して, その値が 0 でない限り 文 を実行するという意味となる. たとえば 式 が 1 ならば, 単純な無限ループとなる.

  * for 文

     形式は,

          for ( 式並び-1; 式; 式並び-2 ) 文

     で, これは

          式並び-1 (を単文並びにしたもの)
          while ( 式 ) {
              文
              式並び-2 (を単文並びにしたもの)
          }

     と等価である.

  * do 文

          do {
              文
          } while ( 式 )

     は, 先に 文を実行してから条件式による判定を行う所が while 文と異なっている.

ループを抜け出す手段として, break 文及び return 文がある. また, ループの制御をある位置に移す手段として continue 文がある.

  * break

     break 文は, それを囲むループを一つだけ抜ける.

  * return

     return 文は, 一般に函数から抜けるための文であり, ループの中からでも有効である.

  * continue

     continue 文は, ループの本体の文の末端に制御を移す. 例えば for 文では, 最後の式並びの実行を行い, while 文では条件式の判定に移る.

さまざまな式
------

主な式の構成要素としては, 次のようなものがある.

  * 加減乗除, 冪

     冪は, ^ により表す. 除算 / は, 体としての演算に用いる. 例えば, 2/3 は有理数の 2/3 を表す. 整数除算, 多項式除算 (剰余を含む演算) には別途組み込み函数が用意されている.

          x+1  A^2*B*afo X/3

  * インデックスつきの変数

     ベクトル, 行列, リストの要素はインデックスを用いることにより取り出せる. インデックスは 0 から始まることに注意する. 取り出した要素がベクトル, 行列, リストなら, さらにインデックスをつけることも有効である.

          V[0] M[1][2]

  * 比較演算

     等しい (==), 等しくない (!=), 大小 (>, <, >=, <=) の 2 項演算がある. 真ならば有理数の 1, 偽ならば 0 を値に持つ.

  * 論理式

     論理積 (&&), 論理和 (||) の 2 項演算と, 否定 (!) が用意されている. 値はやはり 1, 0 である.

  * 代入

     通常の代入は = で行う. このほか, 算術演算子と組み合わせて特殊な代入を行うこともできる. (+=, -=, *=, /=, ^=)

          A = 2  A *= 3 (これは A = A*3 と同じ; その他の演算子も同様)

  * 函数呼び出し

     函数呼び出しも式の一種である.

  * ++, --

     これらは, 変数の前後について, それぞれ次のような操作, 値を表す.

          A++  値は元の A の値, A = A+1
          A--  値は元の A の値, A = A-1
          ++A  A = A+1, 値は変化後の値
          --A  A = A-1, 値は変化後の値

プリプロセッサ
-------

Asir のユーザ言語は C 言語を模したものである. C の特徴として, プリプロセッサ cpp によるマクロ展開, ファイルのインクルードがあるが, Asir においてもユーザ言語ファイルの読み込みの際 cpp を通してから読み込むこととした. これによりユーザ言語ファイル中で #include, #define, #if などが使える.

  * #include

     cpp に特に引数を渡さないため, インクルードファイルは, #include が書かれているファイルと同じディレクトリでサーチされる.

  * #define

     これは, C におけるのと全く同様に用いることができる.

  * #if

     /*, */ によるコメントは入れ子にできないので, プログラムの大きな部分をコメントアウトする際に, #if 0, #endif を使うと便利である.

次の例は, defs.h にあるマクロ定義である.

     #define ZERO 0
     #define NUM 1
     #define POLY 2
     #define RAT 3
     #define LIST 4
     #define VECT 5
     #define MAT 6
     #define STR 7
     #define N_Q 0
     #define N_R 1
     #define N_A 2
     #define N_B 3
     #define N_C 4
     #define V_IND 0
     #define V_UC 1
     #define V_PF 2
     #define V_SR 3
     #define isnum(a) (type(a)==NUM)
     #define ispoly(a) (type(a)==POLY)
     #define israt(a) (type(a)==RAT)
     #define islist(a) (type(a)==LIST)
     #define isvect(a) (type(a)==VECT)
     #define ismat(a) (type(a)==MAT)
     #define isstr(a) (type(a)==STR)
     #define FIRST(L) (car(L))
     #define SECOND(L) (car(cdr(L)))
     #define THIRD(L) (car(cdr(cdr(L))))
     #define FOURTH(L) (car(cdr(cdr(cdr(L)))))
     #define DEG(a) deg(a,var(a))
     #define LCOEF(a) coef(a,deg(a,var(a)))
     #define LTERM(a) coef(a,deg(a,var(a)))*var(a)^deg(a,var(a))
     #define TT(a) car(car(a))
     #define TS(a) car(cdr(car(a)))
     #define MAX(a,b) ((a)>(b)?(a):(b))

オプション指定
-------

ユーザ定義関数が N 変数で宣言された場合, その関数は, N 変数での呼び出しのみが許される.

     [0] def factor(A) { return fctr(A); }
     [1] factor(x^5-1,3);
     evalf : argument mismatch in factor()
     return to toplevel

不定個引数の関数をユーザ言語で記述したい場合, リスト, 配列を用いることで可能となるが, 次のようなより分かりやすい方法も可能である.

     % cat factor
     def factor(F)
     {
         Mod = getopt(mod);
         ModType = type(Mod);
         if ( ModType == 1 ) /* 'mod' is not specified. */
             return fctr(F);
         else if ( ModType == 0 ) /* 'mod' is a number */
             return modfctr(F,Mod);
     }

     [0] load("factor")$
     [1] factor(x^5-1);
     [[1,1],[x-1,1],[x^4+x^3+x^2+x+1,1]]
     [2] factor(x^5-1|mod=11);
     [[1,1],[x+6,1],[x+2,1],[x+10,1],[x+7,1],[x+8,1]]

2 番目の factor() の呼び出しにおいて, 関数定義の際に宣言された引数 X^5-1の後ろに |MOD=11 が置かれている. これは, 関数実行時に, MOD という keyword に対して 11 という値を割り当てることを指定している. これをオプション指定と呼ぶことにする. この値は getopt(mod) で取り出すことができる. 1 番目の呼び出しのように MOD に対するオプション指定がない場合には, getopt(mod) は型識別子 -1 のオブジェクトを返す. これにより, 指定がない場合の動作を if 文により記述できる. | の後ろには, 任意個のオプションを, , で区切って指定することができる.

     [100] xxx(1,2,x^2-1,[1,2,3]|proc=1,index=5);