|
Risa/Asir InternalsRisa/Asir 内部仕様Version 20000530May 2000by Masayuki Noro
Risa/Asir の構成engineengine は, Risa の内部形式を入力, 出力とする関数の集まりである. 主な関数として, 一つの型に対する四則演算のセットがある. また, 例えば, 多項式に対する既約因子分解など, ある型に対して固有の 演算が用意されている場合がある. 他に, 整数に対する基数変換 (10 進数と 16 進数の変換など) などが含まれる. parserユーザからの入力は一般に可読な文字列として与えられる. 一方で engineの受 け取れる入力は, 後で述べるような C 言語の構造体で表現された内部形式であ る. よって, ユーザと対話を行うアプリケーションとしては, ユーザからの文字 列入力を内部形式に変換する必要がある. Asir においては, これを
の 2 段階で行っている. parser においては, まず文字列を一まとまりの 部分文字列 (token) の列として切り分ける. この切り分けを字句解析 と呼ぶ. 各 token は属性を付与される. 例えば 123+456 は 123, +, 456 の 3 つの token に分解され, 123, 456 は式, + は演算子 の属性を付与される. parser は token を順に読んで, 自らが持つ文法定義 と参照しながら tree を作っていく. この tree の仕様が中間言語である. これらの操作および中間言語については後に詳しく述べる. interpreterparser で得られた tree は, 入力文字列を中間言語で書き表したものである. interpreter は tree の根の部分から順に解釈し, 必要があれば engine の関数 を呼び出しながら, 最終的に内部形式であるデータを生成する. tree の各 node は
なる形をしている. interpreter は, 識別子をみて実行すべき仕事および引数の 意味を知る. 必要があれば, それ自身中間言語 tree で表現されている引数を interpreter で内部形式に変換しながら, 識別子に対応する関数を呼び出す. interpreter についても後で詳述する. データ型
この章では, 様々な object の定義, 生成, 演算について解説する. 各々の
object のデータ構造は, C の構造体として表現される. しかし, 実際に函
数に引数として渡されるのは, それらへのポインタである. 一般に, 構造体その
ものは, `o' で始まる名前をもち, それへのポインタは, 構造体の名前か
ら `o' をとったものとして Risa objectstruct oObj { object の共通部分 short id; 識別子 short pad; }; typedef struct oObj *Obj;
Risa object とは構造体
数struct oNum { 数の共通部分 short id; 識別子 (= O_N) char nid; 数識別子 char pad; }; typedef struct oQ *Q;
数は, 多項式と同レベルの object として扱われ, 多項式に対する演算ルー
チンの入力として同様に用いることができる. メンバ
四則以下の四則は数に対するトップレベルの函数である. 同一の数識別子を持つ数の 演算では, 結果はその識別子をもつ数になる. 倍精度浮動小数と有理数の演算 結果は倍精度浮動小数, 倍精度浮動小数と PARI bigfloat の演算結果は PARI bigfloat, 有理数と有限体の元の演算結果は有限体の元となる. #include "ca.h" addnum(Num a,Num b,Num *rp) *rp = a + b subnum(Num a,Num b,Num *rp) *rp = a - b mulnum(Num a,Num b,Num *rp) *rp = a * b divnum(Num a,Num b,Num *qp,Num *rp) *qp = a / b pwrnum(Num a,Num e,Num *rp) *rp = a ^ e int compnum(Num a,Num b) ある規則による比較. 有理数の場合は a=b のとき 1, a>b のとき 1, a<b のとき -1. 有理数struct oN { 自然数 int p; 桁数 int b[1]; BASE-進表示の各桁 }; typedef struct oN *N; struct oQ { 有理数 short id; 識別子 (= O_N) char nid; 数識別子 (= N_Q) char sgn; 符号 (= 1 or -1) N nm; 分子 N dn; 分母 (整数を表す時 0) }; typedef struct oQ *Q;
有理数は自然数を用いて定義されるが, 自然数自体は Risa object ではな
い. 自然数は, `include/base.h'で定義される数 有理数は自然数を分母分子とし, 符号を持つ. 有理数は常に既約分数である. 分 母を 0 とすることにより整数を表現する. 自然数の生成#include "ca.h" bnton(int Base,N a,N *rp) Base-進表示された a を BASE-進表示に変換する.
文字列で表された(例えば 10進の)多倍長数を内部形式に変換する場合,
自然数の四則#include "ca.h" addn(N a,N b,N *rp) *rp = a + b int subn(N a,N b,N *rp) *rp = |a - b| return sgn(a-b) muln(N a,N b,N *rp) *rp = a * b divn(N a,N b,N *qp,N *rp) *qp = a / b (商) *rp = a mod b (剰余) pwrn(N a,int e,N *rp) *rp = a ^ e gcdn(N a,N b,N *rp) *rp = gcd(a,b) int cmpn(N a,N b) sgn(a-b)
... N n1,n2,n3; ... addn(n1,n2,&n3); ... 有理数の生成#include "ca.h" NTOQ(n,s,q) (macro; N n; int s; Q q) sgn = s, nm = n, dn = 0 なる有理数(整数) q を生成する. NDTOQ(n,d,s,q) (macro; N n,d; int s; Q q) sgn = s, nm = n, dn = d なる有理数 q を生成する. STOQ(n,q) (macro; int n; Q q) 単整数 n を 有理数 (整数) に変換する. いずれのマクロも必要な領域はマクロ内で確保される. 有理数の四則#include "ca.h" addq(Q a,Q b,Q *rp) *rp = a + b subq(Q a,Q b,Q *rp) *rp = a - b mulq(Q a,Q b,Q *rp) *rp = a * b divq(Q a,Q b,Q *qp) *qp = a / b invq(Q a,Q *rp) *qp = 1 / a pwrq(Q a,Q e,Q *rp) *rp = a ^ e int cmpq(Q a,Q b) sgn(a-b) 倍精度浮動小数struct oReal { 倍精度浮動小数 short id; 識別子 (= O_N) char nid; 数識別子 (= N_R) char pad; double body; 倍精度浮動小数 }; typedef struct oReal *Real; 倍精度浮動小数の生成, 変換#include "ca.h" MKReal(a,b) (macro; double a; Real b) body が a である倍精度浮動小数 b を生成する. double RatnToReal(Q a) 有理数 a を倍精度浮動小数に変換する. マクロで必要な領域はマクロ内で確保される. 四則いずれも, 浮動小数, 有理数両方を入力にとれる. 結果はすべて浮動小数となる. #include "ca.h" addreal(Num a,Num b,Real *rp) *rp = a + b subreal(Num a,Num b,Real *rp) *rp = a - b mulreal(Num a,Num b,Real *rp) *rp = a * b divreal(Num a,Num b,Real *rp) *rp = a / b pwrreal(Num a,Num e,Real *rp) *rp = a ^ e int cmpreal(Num a,Num b) sgn(a-b) 代数的数struct oAlg { 代数的数 short id; 識別子 (= O_N) char nid; 数識別子 (= N_A) char pad; struct oObj *body; 代数的数を表す多項式または有理式 }; typedef struct oAlg *Alg; struct oV { root char *name; 名前 pointer attr; 定義多項式 (主変数は #n ) pointer priv; 対応する不定元 (t#n) へのポインタ }; typedef struct oV *V; struct oVL { root リスト V v; root struct oVL *next; 次へのポインタ }; typedef struct oVL *VL; extern VL ALG; 定義済み root のリスト
代数的数は, root と呼ばれる特殊な代数的数の有理数係数多項式または有理式
として表現される. root とは, それまで定義された root で表される代数的
数を係数とする 1 変数多項式の根として定義される. root は, 定義が新しい
順に, 多項式に対する変数リストと同じ形式で, root リストとして
大域変数 代数的数の生成#include "ca.h" MKAlg(a,b) (macro; Obj a; Real b) 生成元の多項式または有理式から代数的数を生成する. void mkalg(P p,Alg *r) マクロで必要な領域はマクロ内で確保される. 四則いずれも, 代数的数, 有理数両方を入力にとれる. 結果はすべて代数的数となる. #include "ca.h" addalg(Num a,Num b,Alg *rp) *rp = a + b subalg(Num a,Num b,Alg *rp) *rp = a - b mulalg(Num a,Num b,Alg *rp) *rp = a * b divalg(Num a,Num b,Alg *rp) *rp = a / b pwralg(Num a,Num e,Alg *rp) *rp = a ^ e int cmpalg(Num a,Num b) root の多項式あるいは有理式として比較する.
小標数素体struct oMQ { 小標数素体の元 short id; 識別子 (= O_N) char nid; 数識別子 (= N_M) char pad; int cont; 小標数素体の元 }; typedef struct oMQ *MQ; extern int current_mod;
標数が 2^29 未満の素体を効率よく扱うための型である. 有限体の各元自体は
属する体に関する情報をもっておらず, 大域変数 小標数素体の元の生成, 変換
#include "ca.h"
ptomp(int m,P p,P *pr)
有理数係数多項式 (有理数を含む) の各係数を, 標数
四則引数は 0 または小標数素体の元に限る. 生成された時点での標数にかかわらず, current_mod を標数として計算する. #include "ca.h" addmi(MQ a,MQ b,MQ *rp) *rp = a + b submi(MQ a,MQ b,MQ *rp) *rp = a - b mulmi(MQ a,MQ b,MQ *rp) *rp = a * b divmi(MQ a,MQ b,MQ *rp) *rp = a / b pwrmi(MQ a,Q e,MQ *rp) *rp = a ^ e int cmpmi(MQ a,MQ b) cont の値を自然数として比較 大標数素体struct oLM { 大標数素体の元 short id; 識別子 (= O_N) char nid; 数識別子 (= N_LM) char pad; struct oN *body; 大標数素体の元 }; typedef struct oLM *LM; 任意標数の素体を扱うための型である. 標数は oN 型の自然数としてある静的変 数に格納される. 各元自体は属する体に関する情報をもっておらず, 設定された 標数の値により演算が行われる. 大標数素体の元の生成, 変換#include "ca.h" void setmod_lm(N p) 自然数 p を大標数素体の標数として設定する. p の素数チェックは行わない. void getmod_lm(N *p) その時点で設定されている大標数素体の標数を返す. 設定されていない場合 0 を返す. void qtolm(Q q,LM *l) 有理数 q を標準的写像によりその時点で設定されている 素体の元に写像した値を生成する. 分母の行き先が 0 の場合 error() 関数 が呼ばれる. void simplm(LM n,LM *r) n の body の, その時点で設定されている標数による剰余を body とする元を生成 する.
四則引数は 0 または大標数素体の元または有理数. 有理数はその時点で設定されている 素体の元に変換される. #include "ca.h" addlm(LM a,LM b,LM *rp) *rp = a + b sublm(LM a,LM b,LM *rp) *rp = a - b mullm(LM a,LM b,LM *rp) *rp = a * b divlm(LM a,LM b,LM *rp) *rp = a / b pwrlm(LM a,Q e,LM *rp) *rp = a ^ e int cmplm(LM a,LM b) body の値を自然数として比較 標数 2 の有限体の元struct oGF2N { GF(2^n) の元 short id; 識別子 (= O_N) char nid; 数識別子 (= N_GF2N) char pad; struct oUP2 *body; GF(2^n) の元 }; typedef struct oGF2N *GF2N; typedef struct oUP2 { GF(2) 上の 1 変数多項式 int w; 配列 b のサイズ unsigned int b[1]; 係数配列 (dense 表現) } *UP2; typedef struct oUP2 *UP2; 標数 2 の有限体を扱うための型である. 標数は UP2 型の多項式としてある静的変 数に格納される. 各元自体は属する体に関する情報をもっておらず, 設定された 標数の値により演算が行われる. 標数 2 の有限体の元の生成, 変換#include "ca.h" void setmod_gf2n(P p) 整数係数多項式 p の各係数を GF(2) の元とみた多項式を, GF(2) の拡大体の 定義多項式として設定する. 多項式の GF(2) 上の既約性はチェックしない. void getmod_gf2n(UP2 *p) その時点で設定されている定義多項式を UP2 形式で返す. void ptogf2n(Obj q,GF2N *l) 整数係数多項式 p の各係数を GF(2) の元とみた多項式を, GF2N 型に変換する. 定義多項式が設定されている, いないにかかわらず, 簡単化は行わない. void gf2ntop(GF2N q,P *l) ptogf2n() の逆操作である. void simpgf2n(GF2N n,GF2N *r) body が, n の body のその時点で設定されている定義多項式による剰余である ような GF2N 型の元を生成する.
四則引数は F(2^n)の元または有理数. 有理数はその時点で設定されているGF(2^n) の元に変換される. #include "ca.h" addgf2n(GF2N a,GF2N b,GF2N *rp) *rp = a + b subgf2n(GF2N a,GF2N b,GF2N *rp) *rp = a - b mulgf2n(GF2N a,GF2N b,GF2N *rp) *rp = a * b divgf2n(GF2N a,GF2N b,GF2N *rp) *rp = a / b pwrgf2n(GF2N a,Q e,GF2N *rp) *rp = a ^ e int cmpgf2n(GF2N a,GF2N b) body の値を GF(2) 上の多項式として比較 多項式struct oV { 変数 (不定元) char *name; 名前 pointer attr; 属性 (通常の不定元では 0) pointer priv; 属性に応じたプライベートデータ }; typedef struct oV *V; struct oVL { 変数リスト V v; 変数 struct oVL *next; 次へのポインタ }; typedef struct oVL *VL; struct oP { 多項式 short id; 識別子 (= O_P) short pad; V v; 主変数 struct oDCP *dc; 次数係数リストへのポインタ }; typedef struct oP *P; struct oDCP { 次数係数リスト Q d; 次数 P c; 係数 ( Q でもよい ) struct oDCP *next; 次へのポインタ }; typedef struct oDCP *DCP; extern VL CO; 定義済み不定元リスト 多項式は, ある変数(主変数)に関する多項式として再帰的に表現される. 次数係数リストは <係数> * <主変数>^<次数>
という多項式の各項を, 降べきの順にリストで表現したものである. 多項式も数
と同様に識別子を持つ. 係数が真に多項式であるか, 数であるかは, その識別子
により判定する. 変数たちはある順序が定められ, 多項式はその変数順序により
構成され, 演算される. 変数順序は 多項式の生成#include "ca.h" MKP(v,dc,p) (macro; V v; DCP dc; P p) 主変数 v, 次数係数リスト dc から多項式 p を生成する. dc の次数が 0 の場合 p = <dc の係数> となる. MKV(v,p) (macro; V v; P p) 変数 v を多項式に変換する. NEXTDC(r,c) (macro; MP r,c) r を次数係数リストの先頭とし, c を末尾とするとき, 末尾に次数係数リストを 追加し, c をその次数係数リストを指すようにする. r が 0 のとき, 次数係数リストが生成され, r および c はその次数係数リストを指す.
四則#include "ca.h" addp(VL vl,P a,P b,P *rp) *rp = a + b subp(VL vl,P a,P b,P *rp) *rp = a - b mulp(VL vl,P a,P b,P *rp) *rp = a * b pwrp(VL vl,P a,Q e,P *rp) *rp = a ^ e compp(VL vl,P a,P b) ある規則による比較. これらの函数の引数として数も同様にとれる. 割り算はその他の演算の項で述べる.
有理式struct oR { 有理式 short id; 識別子 (= O_R) short reduced; 既約分数のとき 1 P nm; 分子 P dn; 分母 }; typedef struct oR *R;
有理式は, 単に分母, 分子という二つの多項式の組合せである. 有理数と異な
り, 必ずしも既約とは限らない. 既約にするためには, 有理式の生成#include "ca.h" PTOR(p,r) (macro; P p; R r) 多項式 p を, 分子 p, 分母 1 の有理式に変換する. 四則
以下の各函数は入力として数, 多項式, 有理式 ( #include "ca.h" addr(VL vl,Obj a,Obj b,Obj *rp) *rp = a + b subr(VL vl,Obj a,Obj b,Obj *rp) *rp = a - b mulr(VL vl,Obj a,Obj b,Obj *rp) *rp = a * b divr(VL vl,Obj a,Obj b,Obj *rp) *rp = a * b pwrr(VL vl,Obj a,Q e,Obj *rp) *rp = a ^ e reductr(VL vl,Obj a,Obj *rp) *rp = a を約分したもの. Listtypedef struct oNODE { list を表すための node pointer body; node の内容 struct oNODE *next; 次への pointer } *NODE; typedef struct oLIST { Risa list object short id; 識別子 (=O_LIST) short pad; struct oNODE *body; list 本体 } *LIST;
list 構造は engine 内部において多用される. 前述の リストの生成#include "ca.h" MKNODE(n,b,nn) (macro; NODE n,nn; pointer b) body = b, next = nn なる NODE n を生成する. MKLIST(a,b) (macro; NODE b; LIST a) body=b なる LIST a を生成する. NEXTNODE(r,c) (macro; NODE r,c) r を node の先頭とし, c を node の末尾とするとき, 末尾に空の node を追加し, c をその空 node を指すようにする. r が 0 のとき, 空 node が生成され, r および c はその node を指す.
NODE r, c; for ( r = 0; ...; ... ) { NEXTNODE(r,c); c->body = ...; } if ( r ) c->next = 0; これにより, list の末尾にデータを追加しながら list を伸ばすことができる. ベクトルstruct oVECT { ベクトル short id; 識別子 (= O_VECT) short pad; pointer *body; 成分配列へのポインタ } *VECT; ベクトルは, 数学的な対象としてのベクトルとして用いられるほか, さまざまな object を成分に持つ一次元配列としても用いられる. 前者の場合, ベクトルどうし の和, 差, スカラー倍, 行列との積が基本演算として提供される. 行列との積を計算する場合, 行列を左からかける場合には, ベクトルは列ベクトル, 右からかける場合には行ベクトルと見なされる. ベクトルの生成#include "ca.h" MKVECT(m,l) (macro; VECT m; int l) 長さ l のベクトル m を生成する. 四則#include "ca.h" void addvect(VL vl,VECT a,VECT b,VECT *rp) *rp = a + b void subvect(VL vl,VECT a,VECT b,VECT *rp) *rp = a - b void mulvect(VL vl,Obj a,Obj b,Obj *rp) *rp = a * b (一方がスカラーすなわち数, 多項式または有理数の場合のスカラー倍) void divvect(VL vl,Obj a,Obj b,Obj *rp) *rp = a / b (スカラー b による 1/b 倍) int compvect(VL vl,P a,Q e) 以下で述べる順序で比較. ベクトルどうしの順序比較は以下による.
行列struct oMAT { 行列 short id; 識別子 (= O_MAT) short pad; pointer **body; 行成分ポインタ配列へのポインタ } *VECT; 行列は, 数学的な対象としての行列として用いられるほか, さまざまなobject を成分に持つ二次元配列としても用いられる. 前者の場合, 行列どうしの和, 差, スカラー倍, 行列, ベクトルとの積が基本演算として提供される. ベクトルとの 積を計算する場合, 行列を左からかける場合には, ベクトルは列ベクトル, 右か らかける場合には行ベクトルと見なされる. 行列の生成#include "ca.h" MKMAT(m,row,col) (macro; MAT m; int row, col) row 行 col 列の行列 m を生成する. 四則#include "ca.h" void addmat(VL vl,MAT a,MAT b,MAT *rp) *rp = a + b void submat(VL vl,MAT a,MAT b,MAT *rp) *rp = a - b void mulmat(VL vl,Obj a,Obj b,Obj *rp) *rp = a * b (スカラー倍, 行列-ベクトルの積, 行列-行列の積) void divmat(VL vl,Obj a,Obj b,Obj *rp) *rp = a / b (スカラー b による除算) void pwrmat(VL vl,MAT a,Obj e,Obj *rp) *rp = a^e (正方行列 a の e 乗) int compmat(VL vl,MAT a,MAT e) 以下で述べる順序で比較. 行列どうしの順序比較は以下による.
文字列struct oSTRING { 文字列 short id; 識別子 (= O_STR) short pad; char *body; 0 終端 C 文字列へのポインタ } *VECT; C の 0 終端文字列の wrapper である. 加算 (文字列の接続), 比較演算のみが 用意されている. 文字列の生成#include "ca.h" MKSTR(m,b) (macro; STRING m; char *b) 0 終端 C の文字列 b を Risa 文字列 object m に変換する. 四則#include "ca.h" void addstr(VL vl,STRING a, STRING b, STRING *rp) *rp = a->body と b->body を繋げた文字列 int compstr(VL vl,STRING a,STRING b) 標準ライブラリ関数 strcmp() による比較結果を返す. 分散表現多項式typedef struct oDL { 指数ベクトル int td; 全次数 int d[1]; 指数ベクトル本体 } *DL; typedef struct oMP { 単項式リスト struct oDL *dl; 指数ベクトルへのポインタ P c; 係数 struct oMP *next; 次の単項式へのポインタ } *MP; typedef struct oDP { 分散表現多項式 short id; 識別子 (=O_DP) short nv; 変数の個数 int sugar; sugar の値 struct oMP *body; 単項式リストへのポインタ } *DP; struct order_pair { ブロックに対する項順序型指定 int order; 項順序型 int length; ブロックの長さ }; struct order_spec { 項順序型指定構造体 int id; 項順序型指定識別子 (0: primitive 項順序; 1:ブロック項順序; 2:行列による項順序) Obj obj; Risa object として与えられた項順序型 int nv; 変数の個数 union { 項順序型指定 int simple; primitive 項順序識別子 struct { ブロック項順序型指定 int length; ブロック数 struct order_pair *order_pair; 各ブロックの項順序型配列へのポインタ } block; struct { 行列による項順序型指定 int row; 列数 int **matrix; 行列本体 } matrix; } ord; };
分散表現多項式は, 多項式を, ある代数系を係数とする単項式の和として表現する.
単項式は, 係数および項 (term) の積である. 項は変数の冪積である. 変数を固定
するとき, 項は, 指数のみを取り出した指数ベクトルで表せる. 上記構造体定義に
おいて,
により行う. 1. は単に変数をリストとして指定することにより行う. 2. は 3 つのカテゴリに分かれる.
この値は通常は呼び出した演算関数によりメンテナンスされるが, 独自に 分散多項式を生成した場合には, 上の規則により sugar の値を計算し 設定する必要がある. 分散表現多項式の生成#include "ca.h" MKDP(n,m,d) (macro; int n; MP m; DP d) 変数の個数が n である単項式リスト m から分散表現多項式を生成する. sugar の値は設定されず, この macro を実行したあとに設定する必要がある. NEWDL(d,n) (macro; int n; DL d) 長さ n の指数ベクトル d を生成する. d の全次数は 0, 各成分は 0 に初期化 される. NEWMP(m) (macro; MP m) 単項式リスト構造体 m を生成する. NEXTMP(r,c) (macro; MP r,c) r を単項式リストの先頭とし, c を末尾とするとき, 末尾に単項式リストを 追加し, c をその単項式リストを指すようにする. r が 0 のとき, 単項式リストが生成され, r および c はその単項式リストを指す. void initd(struct order_spec *spec) 項順序構造体 spec を現在の項順序として指定する. void ptod(VL vl, VL dvl, P p, DP *pr) 変数順序 vl の元で構成された(再帰表現)多項式 p を, 変数順序 dvl のもとで, 現在設定されている項順序型に従って分散表現多項式に変換し *pr とする.
四則#include "ca.h" void addd(VL vl,DP a,DP b,DP *rp) *rp = a + b void subd(VL vl,DP a,DP b,DP *rp) *rp = a - b void muld(VL vl,DP a,DP b,DP *rp) *rp = a * b compd(VL vl,DP a,DP b) 以下の順序で比較
その他の演算除算#include "ca.h" divsrp(vl,a,b,qp,rp) *qp = a / b; *rp = a % b; (多項式としての商, 剰余) VL vl; P a,b,*qp,*rp; premp(vl,a,b,rp) *rp = lc(b)^(deg(a)-deg(b)+1)*a % b (擬剰余) VL vl; P a,b,*rp;
一般に多変数多項式に対しては, 必ずしも除算が実行できるとは限らない.
GCD#include "ca.h" ezgcdp(vl,a,b,rp) *rp = gcd(pp(a),pp(b)); VL vl; P a,b,*rp; ezgcdpz(vl,a,b,rp) *rp = gcd(a,b); VL vl; P a,b,*rp; pcp(vl,a,pp,cp) *pp = pp(a); *cp = cont(cp); VL vl; P a,*pp,*cp;
代入#include "ca.h" substp(vl,a,v,b,rp) *rp = a の v に b を代入 VL vl; P a,b,*rp; V v; substr(vl,a,v,b,rp) *rp = a の v に b を代入 VL vl; R a,b,*rp; V v; 微分#include "ca.h" diffp(vl,a,v,rp) *rp = a を v で偏微分 VL vl; P a,*rp; V v; pderivr(vl,a,v,rp) *rp = a を v で偏微分 VL vl; R a,*rp; V v; 終結式#include "ca.h" resultp(vl,v,a,b,rp) *rp = a と b の終結式 (符号は除く) VL v; P a,b,*rp; 因数分解#include "ca.h" fctrp(vl,a,dcp) *dcp = a を有理数上で因数分解したもの VL vl; P a; DCP *dcp; sqfrp(vl,a,dcp) *dcp = a の有理数上で無平方分解したもの VL vl; P a; DCP *dcp;
因数分解の結果は a = c * b (c は有理数, b は整数上原始的な多項式)
と分解した後, マクロ, 大域変数macro`ca.h' で定義される主なマクロは次の通りである. 一般マクロ
述語
メモリ割り当て器
これらはすべて領域の先頭ポインタを返す. cell allocators
主な大域変数
PaserParser の構成parser は Asir 言語で書かれた文字列を中間言語に変換する. paser は 次のものから構成される.
中間言語typedef struct oFNODE { 式を表すノード fid id; 識別子 pointer arg[1]; 引数配列 (長さ可変) } oFNODE *FNODE; typedef struct oSNODE { 文を表すノード sid id; 識別子 int ln; 行番号 pointer arg[1]; 引数配列 (長さ可変) } oSNODE *SNODE;
interpreter の入力となる中間言語の構成は極めて単純である.
中間言語の構成要素は 文 および 式 である.
Asir 言語による入力文字列は, 文の並びとして parse され,
文に対する中間言語定義で分かるように, 文は最終的には式から構成されている.
文中に現れた式は, parser によって解析され,
字句解析字句解析部では, 空白, タブ, 改行をスキップしたあとの最初の文字によって 最初の分類を行う.
名前管理Asir においては, 不定元, 関数, プログラム変数という 3 つのカテゴリ別に 名前が管理されている. 不定元
不定元は変数リスト 関数typedef struct oFUNC { Asir 関数 char *name; 関数名 int argc; 引数の個数 int type; PARI 関数型 aid id; 型 (未定義, 組み込み, ユーザ, 純, PARI) union { void (*binf)(); 組み込み関数 struct oUSRF *usrf; ユーザ定義関数構造体 struct oPF *puref; 純関数 } f; } *FUNC; typedef struct oUSRF { ユーザ定義関数 char *fname; 関数名 short vol; 未使用 int startl,endl; ファイル内での開始, 終了位置 NODE args; 仮引数リスト VS pvs; 局所プログラム変数配列 char *desc; 関数の説明 struct oSNODE *body; 文リスト(関数本体) } *USRF; typedef struct oPF { 純関数 char *name; 関数名 int argc; 引数の個数 Obj body; ユーザ定義純関数の本体 V *args; 引数配列 Obj *deriv; 偏導関数配列 NODE ins; 関数インスタンスリスト int (*pari)(); PARI 呼び出し関数 double (*libm)(); C 数学関数 int (*simplify)(); simplifier } *PF; struct oV { 不定元(再掲) char *name; pointer attr; 属性 pointer priv; }; extern NODE sysf,noargsysf; 組み込み関数リスト extern NODE usrf; ユーザ定義関数リスト extern NODE parif; PARI 関数リスト extern NODE pflist; 純関数リスト
関数には, 組み込み関数, ユーザ定義関数, PARI 関数および純関数がある. い
ずれも 関数構造体
ユーザ定義関数は, 呼び出し時あるいは関数定義時に,
PARI 関数は, 実際の計算に PARI ライブラリを用いるという点を除けば,
組み込み関数と同等である. ただし, PARI ライブラリは PARI bigfloat
を結果として返すので, double float の結果を得たい場合のために,
C の 組み込み, ユーザ定義, PARI 関数は, 引数を用いて計算を行い, Risa object を結果として返す, 通常のプログラミング言語の意味での関数だが, 純関数は, 引数のみが評価されて, その引数を持つ関数呼び出しそのものを返す. Asir の実行例を示す. [0] A=sin(x); sin(x); [1] type(A); 2 <--- 純関数は多項式 [2] vtype(A); 2 <--- 属性は純関数 [3] args(A); [x] <--- 引数リスト [4] B=functor(A); sin <--- 関数子 [5] type(B); 2 <--- 関数子も多項式 [4] vtype(B); 3 <--- 属性は関数子
この例では, プログラム変数typedef struct oVS { プログラム変数配列 unsigned int n; 現在含まれる変数の個数 unsigned int asize; 割り当てられた配列の長さ unsigned int at; unsigned int level; struct oFUNC *usrf; struct oPV *va; 配列本体 NODE opt; 未使用 } *VS; typedef struct oPV { プログラム変数 char *name; 変数名 sort attr,type; pointer priv; } *PV; extern VS GPVS; 大域変数配列 extern VS CPVS; 現在の変数配列 extern VS EPVS; extern 宣言された変数配列 extern VS APVS; 計算結果を格納する配列
Asir においては, プログラム変数のスコープは, 大域変数と, プログラム内で
の局所変数の 2 レベルに単純化されている. 変数は, 現れた時点でいずれかの
プログラム変数として登録される. 関数の外で現れたプログラム変数は, 大域変
数配列に登録される. 関数定義内で現れたプログラム変数は, 関数定義固有の局
所変数配列に登録され, 中間言語において, プログラム変数参照はインデックスにより行われる. 関数 内での変数参照は, 通常は局所変数配列内の変数に対するインデックスが用いら れるが, extern 宣言されている変数に関しては, 同名の大域変数配列の変数に 対するインデックスが用いられる. この場合, 実際にはこの区別はインデックス の最上位ビットを 1 にすることで行っている. InterpreterInterpreter の構成
interpreter は, 中間言語の構成要素である 式の解釈実行pointer eval(FNODE f) 式 f の解釈実行 pointer evalf(FUNC f,FNODE a, FNODE opt) 関数 f の実行 struct oAFUNC { 算術演算テーブル void (*add)(); 和 void (*sub)(); 差 void (*mul)(); 積 void (*div)(); 商 void (*pwr)(); 冪 void (*chsgn)(); 符号反転 void (*comp)(); 比較 }; struct oAFUNC afunc[] = { /* ??? */ {0,0,0,0,0,0,0}, /* O_N */ {addnum,subnum,mulnum,divnum,pwrnum,chsgnnum,compnum}, /* O_P */ {addp,subp,mulp,divr,pwrp,chsgnp,compp}, /* O_R */ {addr,subr,mulr,divr,pwrr,chsgnr,compr}, /* O_LIST */ {notdef,notdef,notdef,notdef,notdef,notdef,complist}, /* O_VECT */ {addvect,subvect,mulvect,divvect,notdef,chsgnvect,compvect}, /* O_MAT */ {addmat,submat,mulmat,divmat,pwrmat,chsgnmat,compmat}, /* O_STR */ {addstr,notdef,notdef,notdef,notdef,notdef,compstr}, /* O_COMP */ {addcomp,subcomp,mulcomp,divcomp,pwrcomp,chsgncomp,compcomp}, /* O_DP */ {addd,subd,muld,divsdc,notdef,chsgnd,compd}, }; void arf_add(VL vl,Obj a,Obj b,Obj *r) *r = a+b void arf_sub(VL vl,Obj a,Obj b,Obj *r) *r = a-b void arf_mul(VL vl,Obj a,Obj b,Obj *r) *r = a*b void arf_div(VL vl,Obj a,Obj b,Obj *r) *r = a/b void arf_remain(VL vl,Obj a,Obj b,Obj *r) *r = a%b void arf_pwr(VL vl,Obj a,Obj b,Obj *r) *r = a^b void arf_chsgn(Obj a,Obj *r) *r = -a int arf_comp(VL vl,Obj a,Obj b) return 1 if a>b; -1 if a<b; 0 if a=b;
文の解釈実行pointer evalstat(SNODE f) 文の解釈実行
文を解釈実行する場合, 文中の式は 単文case S_SINGLE: val = eval((FNODE)FA0(f)); break;
この場合, 複文case S_CPLX: for ( tn = (NODE)FA0(f); tn; tn = NEXT(tn) ) { if ( BDY(tn) ) val = evalstat((SNODE)BDY(tn)); if ( f_break || f_return || f_continue ) break; } break;
この場合, If 文case S_IFELSE: if ( evalnode((NODE)FA1(f)) ) val = evalstat((SNODE)FA2(f)); else if ( FA3(f) ) val = evalstat((SNODE)FA3(f)); break;
第 0 引数はダミー, 条件式リストである第 1 引数を For 文case S_FOR: evalnode((NODE)FA1(f)); while ( 1 ) { if ( !evalnode((NODE)FA2(f)) ) break; val = evalstat((SNODE)FA4(f)); if ( f_break || f_return ) break; f_continue = 0; evalnode((NODE)FA3(f)); } f_break = 0; break;
第 0 引数はダミー, 第 1 引数は初期化のための式リストである. まずこれを
ユーザ関数の実行pointer evalf(FUNC f, FNODE a, FNODE opt) 関数 f を引数 a, option opt で実行
/* ユーザ関数の解釈実行 */ case A_USR: /* 引数リストを評価して LIST オブジェクトとする */ args = (LIST)eval(a); /* ローカル変数 template */ pvs = f->f.usrf->pvs; if ( PVSS ) { /* 既に関数内の場合, その関数内での現関数呼び出しの位置の記録 */ ((VS)BDY(PVSS))->at = evalstatline; level = ((VS)BDY(PVSS))->level+1; } else level = 1; /* スタックフレームを生成, リストに追加, 現在の変数配列とする */ MKNODE(tn,pvs,PVSS); PVSS = tn; CPVS = (VS)ALLOCA(sizeof(struct oVS)); BDY(PVSS) = (pointer)CPVS; CPVS->usrf = f; CPVS->n = CPVS->asize = pvs->n; CPVS->level = level; CPVS->opt = opts; if ( CPVS->n ) { CPVS->va = (struct oPV *)ALLOCA(CPVS->n*sizeof(struct oPV)); bcopy((char *)pvs->va,(char *)CPVS->va, (int)(pvs->n*sizeof(struct oPV))); } /* ステップ実行のためのレベルをアップデート */ if ( nextbp ) nextbplevel++; /* 仮引数に実引数を代入 */ for ( tn = f->f.usrf->args, sn = BDY(args); sn; tn = NEXT(tn), sn = NEXT(sn) ) ASSPV((int)FA0((FNODE)BDY(tn)),BDY(sn)); /* 関数本体を実行 */ val = evalstat((SNODE)BDY(f->f.usrf)); /* フラグ, スタックフレームをリセット */ f_return = f_break = f_continue = 0; poppvs(); break; デバッガユーザ関数の実行は文を単位として行われるが, 文の実行前にデバッグモードに 入る場合がある. デバッグモードでは, 以下のような機能が提供される.
デバッグモードへの移行デバッグモードへの移行は次のような状況で起こる.
ステップ実行
デバッガにおけるステップ実行コマンドには
この機能を実現するために, 二つの変数が用意してある.
また /* ステップ実行中で nextbplevel が非正 */ if ( nextbp && nextbplevel <= 0 && f->id != S_CPLX ) { nextbp = 0; /* デバッグモードに入る */ bp(f); } これらによりステップ実行は次のように実現される.
ブレークポイントの設定
ブレークポイントの設定は, 対象となる文の前に, ブレークポイント文
(文識別子 デバッグモードにおける変数の参照
現在実行中の関数は, その関数構造体へのポインタ (型
トップレベルからその関数に至る呼び出し列に対応するプログラム変数スタック
リストは, node 変数 以上により, 現在実行中の関数に至る呼び出し列中の関数名, 各関数における 変数の値などにアクセスすることができる. メモリ管理メモリ管理機構
注意すべきことは, 通常の void GC_init() 初期化ルーチン. 起動時に実行する. void *GC_malloc(int n) n bytes の領域を割り当てる. 領域は 0 で初期化される. void *GC_malloc_atomic(int n) ポインタを含むことがないと保証される n bytes の領域を割り当てる. GC_free(void *p) p の指す領域を開放する. Risa では, ある領域がどこからどの位指されている か一般には判断できないので, 通常はこの関数が使用されることはない. 関数内で割り当てたバッファの開放などに用いることはできる.
通常は Risa におけるメモリの使用Risa における各演算関数について共通の振舞いとして, 結果として生成される object の内部で, 入力である object の各部への参照が行われている可能性 がある, ということがある. 次の例は, 多項式の加算関数である. この中で, 例えば先頭の項の次数が異なる 場合には, 係数(へのポインタ)がそのまま結果にコピーされている. また, 引数の一方の次数係数リストの末尾までたどった時に, 一方の次数係数リストが 残っている場合には, その残りがそのまま結果の次数係数リストにつながれる. #include "ca.h" void addp(vl,p1,p2,pr) VL vl; P p1,p2,*pr; { DCP dc1,dc2,dcr0,dcr; V v1,v2; P t; if ( !p1 ) *pr = p2; else if ( !p2 ) *pr = p1; else if ( NUM(p1) ) if ( NUM(p2) ) addnum(vl,p1,p2,pr); else addpq(p2,p1,pr); else if ( NUM(p2) ) addpq(p1,p2,pr); else if ( ( v1 = VR(p1) ) == ( v2 = VR(p2) ) ) { for ( dc1 = DC(p1), dc2 = DC(p2), dcr0 = 0; dc1 && dc2; ) switch ( cmpq(DEG(dc1),DEG(dc2)) ) { case 0: addp(vl,COEF(dc1),COEF(dc2),&t); if ( t ) { NEXTDC(dcr0,dcr); DEG(dcr) = DEG(dc1); COEF(dcr) = t; } dc1 = NEXT(dc1); dc2 = NEXT(dc2); break; case 1: NEXTDC(dcr0,dcr); DEG(dcr) = DEG(dc1); COEF(dcr) = COEF(dc1); dc1 = NEXT(dc1); break; case -1: NEXTDC(dcr0,dcr); DEG(dcr) = DEG(dc2); COEF(dcr) = COEF(dc2); dc2 = NEXT(dc2); break; } if ( !dcr0 ) if ( dc1 ) dcr0 = dc1; else if ( dc2 ) dcr0 = dc2; else { *pr = 0; return; } else if ( dc1 ) NEXT(dcr) = dc1; else if ( dc2 ) NEXT(dcr) = dc2; else NEXT(dcr) = 0; MKP(v1,dcr0,*pr); } else { while ( v1 != VR(vl) && v2 != VR(vl) ) vl = NEXT(vl); if ( v1 == VR(vl) ) addptoc(vl,p1,p2,pr); else addptoc(vl,p2,p1,pr); } } このように, Risa の演算関数では, 一見不要になった中間的な結果でも, その 部分式が最終結果に用いられていることがあるため, 注意が必要である. 特に, 配列を書き換える必要がある場合などには, 配列そのものを新しく生成して, 成 分をコピーしてから用いる必要がある. 文献
This document was generated on 20 October 2002 using texi2html 1.56k. |