インタプリタはPrologのコードを逐次解釈しながら実行する処理系です。
一括翻訳するコンパイラに比べて処理速度は落ちますが、ソースプログラムをそのままヒープ領域(プログラム領域)に読み込むだけで即実行でき、実行の中断、任意の時点でトレース(述語毎に述語名や引数の値を表示しながらの実行)などができるため、プログラムの開発段階でよく用いられます。
AZ-Prologが提供するインタプリタには以下の3種類があります。
(1).ウインドウアプリケーションインタプリタ
(2).コンソールアプリケーションインタプリタ
(3).CGIインタプリタ
最初の2つはユーザが直接コマンドを実行して利用しますが、3つ目はその名の通りPrologで書かれたCGIプログラムを解釈実行するためのもので、WEBサーバ(HTTPサーバ)によって起動されます。
prolog.exe(Linux版またはMac版ではprolog)を起動します。
Windows版ではインストール時にスタートメニューにAZ-Prologが追加されますが、起動されるのはこのインタプリタです。
トップレベル(この語句については5-2参照)は、
|?-
となります。ここからプログラムを読み込んだり、実行したりすることができます。バックスペースキー2度押しで"?-"を消せば、prolog_c(.exe)と同様、ここからプログラム(節)を入力することもできます。
内蔵エディタ(9-11参照)、キーバッファ機能(5-6-2.リストア参照)および任意の場所へのカーソル移動(9-11-3.AzEditのカスタマイズ参照)などの関連述語が利用できます。
prolog_c.exe(Linux版またはMac版ではprolog_c)を起動します。トップレベルは
|
となります。ここからプログラム(節)を入力することもできますし、入力を「?-」から始めれば、prolog(.exe)と同様、プログラムを実行することができます。
内蔵エディタ、キーバッファ機能およ関連述語は利用できませんが、コンソールのコマンドラインで他のコマンドとパイプでつなげたり、出力をリダイレクトしたりして利用することもできます。以下はその例です。
<例:Windows>
C:¥>echo ?-['queen.pl'],q(5),halt. | prolog_c -nologo > tmp
<例:LinuxまたはMac版>
$ echo "?-['queen.pl'],q(5),halt." | prolog_c -nologo > tmp
となります。ここからプログラム(節)を入力することもできますし、入力を「?-」から始めれば、prolog(.exe)と同様、プログラムを実行することができます。
prologcgi.exe(Linux版またはMac版はprologcgi)
このインタプリタを起動するのは、ユーザ入力によるコマンド実行ではなく、WEBサーバ(HTTPサーバ)です。
色々なサーバが存在しますが、本マニュアルでは、Apacheを前提として説明しています。
cgi-binなどの下におかれた所定の形式(これについては「9-4.CGIインターフェースとユーティリティ」を参照してください)のPrologプログラムの先頭にこのインタプリタを指定することで、ユーザがブラウザからHTTPリクエストを送るとWEBサーバからProlog(CGI)プログラムが呼ばれ、その時に起動されます。
CGIインタプリタへのパスが通っているか、apache¥binの下にCGIインタプリタがおかれていれば、CGIファイルの先頭に次の書式でインタプリタを指定します。
ファイル先頭の記述<例>
#! prologcgi
フルパスで記述することで任意のバージョン(あれば)のCGIインタプリタを起動することができます。
ファイル先頭の記述<例>
#! C:¥AZ-Prolog.800x64¥bin¥prologcgi
CGIの書き方は、以下の場所にサンプルがありますのでご参照ください。
Windows版 | :%AZProlog%¥sample¥cgi_demo |
Linux版 | :${AZProlog}/share/azprolog/sample/cgi_demo |
Mac版 | :${AZProlog}/share/azprolog/sample/cgi_demo |
トップレベルとは、インタプリタ立上げ直後にプロンプト(入力促進記号)が出て入力待ちになっている状態を指します。
通常、この状態でPrologの質問(ゴール列)を入力します。
AZ-Prologでは、トップレベルでの入力の手間を軽減する為、過去に入力・実行した履歴を文字列(改行除く)として10行分まで記憶しています。
そして各種コマンド(コントロールキーと特定のアルファベットキーを同時に押す)操作により、その入力履歴を表示したり、その中からある文字列を選んで呼出し、更にその文字列に対して一文字削除・挿入などの編集も出来ます。これらを総称してヒストリ・テンプレート機能と呼びます。
では、そのオペレーションを例を挙げて説明しましょう。
過去に
| ?-a(1). : | ?-b(1). : | ?-a(2). : | ?-b(2). : | ?-a(3). : | ?-b(3). : | ?-a(4). : | ?-b(4). : | ?-a(5). : | ?-b(5).
などの文字列(質問)を入力していたとします。
ここで「CTRL-X」を入力すると…
| ?-^X
HISTORY
1: ?-b(5).
2: ?-a(5).
3: ?-b(4).
4: ?-a(4).
5: ?-b(3).
6: ?-a(3).
7: ?-b(2).
8: ?-a(2).
9: ?-b(1).
10: ?-a(1).
と表示されます。
このように最後に入力された行の文字列から順に表示されます。
入力履歴を遡って直前の入力文字列を呼出したい場合は、「CTRL-U」を1回入力します。続けて「CTRL-U」を入力すると、更に前の文字列が順々に呼び出されます。また「CTRL-N」で逆順にも呼出せます。
| ?-b(5).^U
↓
| ?-a(5).^U
↓
| ?-b(4).^U
:
:
と表示され、再度入力したい文字列が表れたら「Enterキー」を押します。
するとその文字列をタイプ入力されたのと同様の動作をします。
「CTRL-U」によって呼び出された文字列は「テンプレートバッファ」と呼ばれるところにも転送されます。
このテンプレートバッファには通常、最後に入力・実行した文字列がセットされますが、「CTRL-U」を押すと、その時呼び出された文字列に置き換わります。
文字列を入力して実行する(Enterキーを押す)前でも、「CTRL-T」でテンプレートバッファにコピーできます。このコマンドは、長い文字列を入力した後で間違いを見つけた時など、実行する前に修正したい場合に使えます。
「CTRL-U」、「CTRL-N」、「CTRL-T」を押した直後は、テンプレートバッファの内容は既に入力行にも現れています。これを編集したい場合は、残念ながらカーソルを修正したい場所に移動して直すことはできないので、バックスペースキーで修正位置までを消すか、「CTRL-Q」を押して一旦全てをクリアします。
「CTRL-Q」を押すと「?-」まで消えてしまいますが、この状態はヒープ領域への節の入力が可能(但し1行のみ)な状態で、質問を入力するには「?-」から始めることになります。でも後述の「CTRL-F」や「CTRL-A」でテンプレートバッファの内容を呼び出すと「?-」から始まるので、安心してください。
では、一旦「CTRL-Q」で入力をクリアして、続いて「CTRL-A」を押してみましょう。「CTRL-A」は現在のテンプレートバッファの内容をトップレベルの入力行にコピーするコマンドです。
テンプレートバッファの内容が「b(5).」であるとすると…
| ?-b(5).
が再び現れるはずです。
バックスペースキーで「?-」の後ろを全て消して、テンプレートバッファの内容を一文字ずつトップレベルにコピーする「CTRL-Fコマンド」を使うと、
| ?-^F
↓ <== 同じ行での変化を表すものとする
| ?-b^F
↓
| ?-b(^F
↓
| ?-b(5^F
↓
| ?-b(5)^F
↓
| ?-b(5).
となります。これを使うと内容の一部を書換える事ができます。
例えば、「b(5).」の引数を「Ⅹ」にしたい場合は…
| ?-^F
↓
| ?-b^F
↓
| ?-b(X <== ここでキーボードから"X"を入力する
↓
| ?-b(X^F
↓
| ?-b(X)^F
↓
| ?-b(X).
また、「b(5).」の述語名を「c」に変更したい場合は…
| ?-c <== キーボードから"c"を入力する ↓ | ?-c^A <== "CTRL-A"を入力すると残り全部がコピーされる ↓ | ?-c(5).
1文字をスキップする「CTRL-Eコマンド」を使うと、例えばテンプレートバッファの内容が「a(X,Y).」の時に、ここから2文字削った「a(X).」という文字列を現在の行にコピーできます。
| ?-^F
↓
| ?-a^F
↓
| ?-a(^F
↓
| ?-a(X^E^E <== ここで"CTRL-E"を2回入力すると",Y"をスキップする
↓
| ?-a(X^A
↓
| ?-a(X).
これとは逆にテンプレートの文字列の途中に新たな文字列を挿入する事も出来ます(挿入モードにする「CTRL-Wコマンド」)。
例えばテンプレートバッファの内容が「a(X).」の時に「a(X,Y).」という文字列を現在の行にコピーしてみましょう。
| ?-^F ↓ | ?-a^F ↓ | ?-a(^F ↓ | ?-a(X^W <== ここで"CTRL-W"を入力すると、挿入モードになる ↓ | ?-a(X,Y <== ここでキーボードから",Y"を入力する ↓ | ?-a(X,Y^A <== テンプレートバッファの残りを呼び出す。 ↓ | ?-a(X,Y).
のようにできます。
以下はヒストリ・テンプレートコマンド一覧です。
<ヒストリー> | |
---|---|
CTRL-X | ヒストリーの表示。 |
<テンプレート> | |
CTRL-F | 入力行にテンプレートバッファから1文字コピーする 。 |
CTRL-A | 入力行にテンプレートバッファに残っている全ての文字をコピーする。 |
CTRL-E | テンプレートバッファ内の1文字をスキップする。 |
CTRL-W | 挿入モードに入る。もう1度押すと挿入モードを解除する。 |
CTRL-T | 入力行の内容をテンプレートにコピーする。 |
CTRL-Q | 入力行をクリアする([?-」も消される)。 |
CTRL-U | 一つ前の履歴で入力行を置き換える。同時にテンプレートバッファにもコピーする。 |
CTRL-N | 一つ後の履歴で入力行を置き換える。同時にテンプレートバッファにもコピーする。 |
AZ-Prologでは処理系内からOSコマンドを実行出来ます。
そのためにOSコマンドを一つだけを実行する「system/1」述語と、一旦OSのコマンドレベルに抜けてしまう「sh/0」述語の2つの標準組込述語があります。
例えば、カレントディレクトリのファイル名を確認するには次の様にします(以下はWindows版DOSコマンドの例です。Linux版またはMac版の場合は"ls"に置き換えてください。但し、lsコマンドの出力は述語呼び出しの直下には表示されません。次に説明するshを実行して、Prologを起動したシェル画面に戻ると出力を確認することができます。)
| ?-system("dir/W").↓
:
:<== ディレクトリを表示する
:
yes
| ?-
また、一旦OSのコマンドレベルに抜けるには…
| ?-sh. <== コマンドプロセッサを起動 Microsoft Windows [Version x.x.xxxx] Copyright (c) 20XX Microsoft Corporation. All rights reserved. C:¥>dir : : <== ディレクトリを表示する : C:¥>exit <== コマンドプロセッサを終了 yes | ?-
とします。
プログラムを入力するには以下のような4つの方法があります。
プログラムをコンソールから入力する場合、およびエディットする場合にはAzEditを使うと効率よく入力できます。 また、ファイルからプログラムを読み込む場合にも一度AzEditを介して読み込んだ方がプログラムリストの参照・プログラムの変更などのときに便利です(本節においては上記のうち1.および2.についてのみ説明します。AzEditの使用方法については「9-11.内蔵エディタ(AzEdit)」を参照してください。なお、Prologの文法については、「4.Prolog言語」において解説されています。)
プログラムはコンソール(端末)で直接入力することができます。
コンソールからプログラムを入力するには次の2通りの方法があります。
インタプリタのトップレベルにおいて「CTRL-H(またはBS KEY、Mac版の場合はdelete KEY)」を2回押すと「?-」が消え、カーソルも2文字分戻ります。この状態でプログラムを入力することができます。但し、この場合は1行限りです。
| ?-^H
↓
| ?^H
↓
| my_append([],X,X).↓
↓
| ?-^H
↓
| ?^H
↓
| my_append([A|X],Y,[A|Z]):-my_append(X,Y,Z).↓
↓
| ?-listing.↓ <== 「listing/0」はヒープ領域の一覧を表示する組込述語
my_append([],X,X).
my_append([A|X],Y,[A|Z]) :-
my_append(X,Y,Z).
yes
| ?-
数行をまとめて入力するには…
| ?-[user].
この後に入力するとインタプリタは、節あるいは指令の入力を待つ状態となります。
インタプリタのトップレベルに戻る場合は「END_OF_INPUT」文字(WindowsはCTRL-Z、LinuxまたはMacはCTRL-D)を入力してください。
この方法で上と同じプログラムを入力してみましょう。下の例はWindows版の場合です。Linux版またはMac版でCTRL-Zを入力すると、インタプリタのプロセスがサスペンド(停止)してしまうので注意してください。
もしうっかり入力して、シェルの画面が表示された時は、fgコマンドを実行してください。元に戻れます。
| ?-[user].↓
| my_append([],X,X).↓
| my_append([A|X],Y,[A|Z]):-my_append(X,Y,Z).↓
| ^Z <== "CTRL-Z"を入力
yes
| ?-listing.↓
my_append([],X,X).
my_append([A|X],Y,[A|Z]) :-
my_append(X,Y,Z).
yes
| ?-
この様に同じ結果に成る事が分ります。
(注)
プログラムをコンソール(端末)からインタプリタに直接入力するのは、その節が永久に必要でないか、または数ステップのプログラムを入力する場合以外はおすすめできません。
プログラムの有効利用のため、プログラムのテキストを含むファイルをエディタで作成して使用することをおすすめします。
Prologプログラムをファイルから読み込む事を「コンサルト(consult)」といいます。
コンサル卜するにはインタプリタのトップレベルにおいて次のように入力します。
| ?-consult('パス名¥ファイル名').↓
または、省略記法で、
| ?-['パス名¥ファイル名'].↓
パス名を省略したときにはカレントディレクトリから読み込みます。
<例>カレントディレクトリのski.plというファイルからプログラムを読み込む。
| ?-['ski.pl'].↓
カレントディレクトリの下のplというディレクトリからlist.plというファイルからプログラムを読み込む。
| ?-['pl¥lisp.pl'].↓
プログラム入力が正常に終わると、コンソールに「yes」およびプロンプトが表示されます。
インタプリタを起動してから初めてあるプログラムを読み込む場合には以上の方法で十分です。
しかし、そうではない場合には不都合なことが起こる場合があります。
と言うのは、コンサルトは以前に読み込んだプログラムがあっても、それらを消去してから新たに読み込むと言うことをしません。つまり、古いプログラムはそのまま残り、それに新しいプログラムが付け加えられます。従って、運が悪いと古いプログラムと新しいプログラムがごちゃまぜになって正常に動かなくなる事もあります(以下の例)。
<例>
| ?-s_new.↓ yes | ?-['my_append.pl'].↓ yes | ?-listing.↓ my_append([],X,X). my_append([A|X],Y,[A|Z]) :- my_append(X,Y,Z). yes | ?-['my_append.pl'].↓ yes | ?-listing.↓ my_append([],X,X). my_append([A|X],Y,[A|Z]) :- my_append(X,Y,Z). my_append([],X,X). my_append([A|X],Y,[A|Z]) :- my_append(X,Y,Z). yes | ?-
「ここで、「s_new/0」はすべてのプログラム(ユーザ定義の節)を消去する述語です。また「listing/0」はプログラムを出力する述語です。
最初に「s_new/0」でプログラムを消去した後、同じファイルを2度続けて「 consult 」した結果、「my_append/3」の定義が2重になっているのがわかります。(もちろん、これは使い方が悪いのであって「 consult 」の欠点ではありません。このような性質を利用してプログラムを複数のファイルに分けておくというようなこともできます。)
consultによる述語の多重定義や、新旧定義の混在を避ける方法として、次の2つがあります。
ひとつは、上の「s_new/0」述語を実行してプログラムをすべて消去してから「 consult 」するという方法です。
もうひとつは、次に述べる「リコンサルト(reconsult)」する方法です。
古い述語定義を新しい定義で上書きするようにしてファイルから読み込むことを「リコンサルト(reconsult)と言います。
リコンサル卜するにはインタプリタのトップレベルでは次のように入力します。
| ?-reconsult('my_append.pl').↓
または、省略記法で
| ?-[-'my_append.pl'].↓
このようにファイル名のまえに「-」を付けると、そのファイルを「 reconsult 」します。
「 reconsult 」すると指定されたファイルの中で定義されている述語が既にインタプリタの中に存在していた場合には、それらを消去してファイル中の定義に置き替えます。したがって上のような事は起こりません。
これらをプログラム中に書いておく事もできますから、ソースファイルをいくつかに分割しておき、必要に応じて次々と読み込んでいく事もできます。
しかしこの方法では処理速度が落ちますので、プログラム領域の十分大きいAZ-Prologではあまり賢明な方法とは言えません。実行途中にレスポンスを悪くするより、最初に全て読み込んでおくことをお勧めします。
<補足1>
コンサルトとリコンサルトでは、DCGトランスレータが内蔵され、読み込みの際ファイル中のDCG記法の節をPrologの節に変換しHeapに登録します。
<例>
< ファイル中のDCG記法の節 > | < Heapに登録される節 > | |
vp([vp,Vt,Np])-->vt(Vt),np(Np). | <変換> | vp([vp,Vt,Np],_2,_3):-vt(Vt,_2,_4),np(Np,_4,_3). |
n([n,cat])-->[cat]. | <変換> | n([n,cat],[cat|_0],_0). |
<補足2>
Prolog起動時のプログラムファイル読み込みは、「3-3-2.起動時に読み込むプログラムファイルの指定」をご参照ください。
【注意】
UTF-8の文字コードプログラムファイルを作成する場合、BOM(バイトオーダーマーク)を付けないでください。
Prologではインタプリタに「質問」を発することによってプログラムの実行が始まります。
また、Prologプログラムに書かれている情報を使って、その質問が正しいかどうかを判定する作業をPrologプログラムの「実行」といいます。
<例>ソースファイル('RIKAI.PL')が次の内容であり、カレントディレクトリにある時・・・
理解する(松尾さん,ワビ). 理解する(松尾さん,サビ). 理解する(ブリキ屋さん,サビ). 理解する(X,風流):- 理解する(X,ワビ). 理解する(X,風流):- 理解する(X,サビ).
(注:このファイルはサンプルプログラムには含まれていません。)
| ?-['RIKAI.PL'].↓ /*ファイル'RIKAI.PL'からプログラムを読み込む*/ yes | ?-listing.↓ /*プログラムのリスト*/ 理解する(松尾さん,ワビ). /*松尾さんは“ワビ”がわかる。*/ 理解する(松尾さん,サビ). /*松尾さんは“サビ”がわかる。*/ 理解する(ブリキ屋さん,サビ). /*ブリキ屋さんは“サビ”がわかる。*/ 理解する(X,風流) :- 理解する(X,ワビ). /*“ワビ”がわかる人は風流を理解する。*/ 理解する(X,風流) :- 理解する(X,サビ). /*“サビ”がわかる人は風流を理解する。*/ yes | ?-理解する(松尾さん,ワビ).↓ /*松尾さんは“ワビ”がわかるか?*/ yes | ?-理解する(ブリキ屋さん,サビ).↓ /*ブリキ屋さんは“サビ”がわかるか?*/ yes | ?-理解する(ブリキ屋さん,ワビ).↓ /*ブリキ屋さんは“ワビ”がわかるか?*/ no | ?-理解する(ブリキ屋さん,風流).↓ /*ブリキ屋さんは風流を理解するか?*/ yes | ?-
このように、インタプリタは質問に対して、それがプログラムから事実かどうかを答えます。
ここでは、プロンプト(?-)に続く「理解する(松尾さん,ワビ)」や「理解する(ブリキ屋さん,サビ)」(これらをゴールと呼びます)が正しいかどうかをインタプリタが判定したわけです。(インタプリタがどのように判定するのかについては「4-3.プログラムの実行」で説明しています。)
あるゴールが正しいということが言えた場合、そのゴールの実行に成功したといいます。逆に、正しいと言えなかった場合には失敗したといいます。
ところで、上の質問はどれも変数を含んでいませんでした。次に変数を含む質問をしてみます。(英大文字は変数を表します。変数についての詳細は「4-1-2.変数」を参照してください。)
| ?-理解する(松尾さん,X).↓ /*松尾さんは何を理解しているか?*/ X = ワビ↓ /*それはワビです。*/ yes | ?-
質問が変数を含む場合には、このようにその変数に何を代入したときに質問が正しくなるかを表示します。ここで「X=ワビ」のところで「↓」を入力したのは、この答えでユーザが満足したからです。これ以外の解をみつけたいときには「;↓」(セミコロン)を入力します。
| ?-理解する(松尾さん,X).↓ /*松尾さんは何を理解しているか?*/ X = ワビ;↓ /*他の解を探せ。*/ X = サビ;↓ /*他の解を探せ。*/ X = 風流;↓ /*他の解を探せ。*/ X = 風流;↓ /*他の解を探せ。*/ no | ?-
もうこれ以上別の解がないときには、このように「no」を表示します。
<変数が2個以上ある場合の例>
| ?-理解する(X,Y).↓ X = 松尾さん, Y = ワビ↓ yes | ?-
これまでの例では、質問がただ一つのゴールのみから成っていました。
質問としていくつものゴールを書き連ねることもできます。
| ?-理解する(X,ワビ),理解する(X,サビ).↓ /*“ワビ”も“サビ”も理解しているのは誰か?*/ X = 松尾さん;↓ /*他の解を探せ。*/ no | ?-
質問の他に、プログラムを起動するものとしてコマンドがあります。コマンドを入力する場合には、インタプリタのコマンドレベルにおいてCTRL-H (またはBS KEY)を2回押して「?-」を消し、その代わりに「:-」と打ちなおしてから質問の時と同じように実行したいゴールを書き並べてください。
| :-['ski.pl'].↓ <== ファイルski.plから読み込め
| ?-
質問とは異なり、コマンドの実行に成功した場合には何も表示されません。変数が含まれている場合にも、別の解を探すようなことはしません。
ただし、失敗した場合には「?」が表示されます。
プログラムの実行をストップさせたり、表示を一時的に止めておきたい時があります。 この節では、AZ-Prologのそのような機能について解説します。
インタプリタが働いているときに「INTERRUPTキー」(CTRL-C)を押す事によって実行を中断させることができます。
(下の例は、無限ループのプログラムです。)
| ?-repeat,fail.↓
INTERRUPT <== CTRL-Cキーを押す
Interrupting A(bort),B(reak),C(ontinue) or O(s level) ?a↓
| ?-
この時以下のようなコマンドが使えます(上の例では“A(bort)”コマンドを使っています)。
A(bort) | 実行を中止しインタプリタはトップレベルにまで戻ります。 標準組込述語の「abort/0」を実行した場合とまったく同じです。 「errorset/2 」による実行の場合には、そのerrorsetによってトラップされます。 |
B(reak) | 実行を保留して、ブレークレベル(後述)を一つ深くします。 標準組込述語の「break/0」を実行した場合とまったく同じです。 |
C(ontinue) | そのまま実行を続けます。 |
O(s) | Prologインタプリタから、コマンドプロセッサヘ一時的に制御を移します。EXITコマンドを実行することによって、Prologの実行を再開できます。 標準組込述語の「sh/0」を実行した場合とまったく同じです。 |
T(race) | このコマンドは、デバッグモードの場合のみ有効です。 トレースを始めます。 標準組込述語の「trace/0」を実行した場合と同じです。 「6.デバッカ」参照してください。 |
Bコマンドでブレークをかけると、一見インタプリタのトップレベルに戻ったように「?-」のプロンプトが出て質問(コマンド)待ちの状態になります。しかし、実際にはそれまで実行されていたプログラムは凍結状態で、すべての情報はそのまま保存されています。この状態をブレークレベルといいます。
あるブレークレベルで他のプログラムを実行中に再び別のブレークをかけることもできます。つまりブレークレベルは多階層に成り得る訳で、この階層の深さもブレークレベルと呼び、数字で表します。ブレークした場合にプロンプトの上の行に表示される鍵カッコで囲まれた数字は、その意味のブレークレベルです。
標準組込述語の「break/0」によって実行を中断した場合とまったく同じ状態になります。
また「unbreak/0」によって実行の再開ができます。
★デバッグモードで実行していた場合でも、break時に起動されるインタプリタのモードはノーマルモードで始まります。
以下の例ではデバッグモードから始まっています。プロンプトの変化に注目してください。デバッグモードでは「||?-」、ノーマルモードでは「|?-」となっています。デバッグモードでの割り込み(INTERRUPT)時のコマンド要求行ではTraceコマンドが増えていることも確認できます。
<例>
||?-repeat,write(a),fail.↓ aaaaaaaaaaaaaaaaaaa INTERRUPT <== CTRL-Cキーを押す Interrupting A(bort),B(reak),C(ontinue),T(race) or O(s level) ?b↓ debug mode off [1] | ?-repeat,write(b),fail.↓ <== 別のプログラムを動かす bbbbbbbbbbbbbbbbbbb INTERRUPT <== CTRL-Cキーを押す Interrupting A(bort),B(reak),C(ontinue) or O(s level) ?b↓ [2] | ?-true.↓ <== 別のプログラムを動かす yes [2] | ?-unbreak.↓ <== 先のプログラムの実行を再開する bbbbbbbbbbbbbbbbbbb INTERRUPT <== CTRL-Cキーを押す Interrupting A(bort),B(reak),C(ontinue) or O(s level) ?a↓ [1] | ?-unbreak.↓ <== 先々のプログラムの実行を再開する aaaaaaaaaaaaaaaaaaa INTERRUPT <== CTRL-Cキーを押す Interrupting A(bort),B(reak),C(ontinue),T(race) or O(s level) ?a↓ LOOP = xxxxx <== ループした総数が表示される ||?-
キーボードからコントロールコードを入力することによって、コンソールに対する出力を制御することができます。
CTRL-S | コンソールに対する出力を保留します。したがって、インタプリタの実行も一時ストップします。 この状態においてINTERRUPTキーを押しても前項の場合(いきなりINTERRUPTキーを押した場合)と同じコマンド要求メッセージが出力されます。 |
CTRL-Q | CTRL-Sによって保留されていたコンソールに対する出力を再開します。 |
<例>
|| ?-repeat,write(a),fail.↓
aaaaaaaaaaaaaaaaaaa INTERRUPT <== CTRL-Sキーを押した後、CTRL-Cキーを押す
Interrupting A(bort),B(reak),C(ontinue),T(race) or O(s level) ?
ここでも、前項の場合(いきなりINTERRUPTキーを押した場合)と同じコマンド要求メッセージが出力されて同じコマンドが使えます。
このマニュアルでは、ファイルやコンソール画面、プリンタなどの装置との入出力を統一的に扱うための抽象的な対象を「ストリーム」と呼んでいます。コンソールやプリンタなどの装置もディスク上のファイルと同等に扱うよう特別のファイル名が与えられています。
このストリームへの入出力方法には、次の二通りがあります。
あらかじめ入出力対象ファイルを指定してカレントストリームを切換えておき、その暗黙のストリームに対して入出力する方法を説明します。
まず、「see/1」「tell/1」「tella/1」等の組込述語で、入出力の対象となるファイルのパス名を引数として指定します。これにより、そのファイルが入出力対象(カレントストリーム)となります。この時、指定されたファイルがまだオープンされていなかった場合は、オープンしてからカレントストリームを切換えます。
カレントストリームを「seen/0」「told/0」という組込述語によってクローズすると、カレント値は一つ前のストリームに切り換えられます。ただし、カレントストリームの既定値(起動時)は、入出力共コンソールです。
したがって、インタプリタ立上げ後一度もストリームが切換えられてないとコンソールに対して入出力がなされます。
★パス名の与え方
ファイルのパス名は、その文字列をシングルクォート「'」で囲ったアトムで指定します。ただし、そのままでアトムとして認められるパス名(ディレクトリ階層の区切り記号¥や/、あるいはピリオド「.」が含まれない、例えばカレントディレクトリにある拡張子なしのファイル名や特殊ファイル名等)の場合には、特にシングルクォート「'」で囲む必要はありません。
例えば「 azedit.pl 」というファイルを現在の入力ストリームとする場合は、
| ?-see('azedit.pl').
を実行すればよい事になります。
この場合は、カレントディレクトリにある「 azedit.pl 」というファイルが現在の入力ストリームになります。
また、Windows版ではドライブ名を明示的に指定する事もできます。その場合にはドライブ名をパス名に含める事も出来ます。
例えば、
| ?-consult('b:queen.pl').
のように指定します。
この場合はBドライブにある「 queen.pl 」というファイルがconsultされます。但し、queen.plはBドライブのカレントディレクトリにあると言う前提です。
★特殊ファイル
入出力をファイルという概念で統一的に扱うために以下のような4個の特別なファイルが用意されています。
パス名 | 装置名 |
con | コンソール(キーボードとディスプレイ)。END_OF_INPUTキーで入力終了 |
user | 同上 |
nul | 入力ファイルとしては空のファイル(ダミーファイル)。 結果的にはここに出力しても何もしなかったのと同じ。 |
edit | 現在のエディタバッファ |
★ファイルエラーモード
その他の機能として、入出力関係の述語がエラーを起こした場合、通常は実行を中断しますが、ただ単に失敗するようにする事もできます。
どちらのモードにするかを「fileerrors/2」という述語によって切り換えることができます。
前項で説明した現在のストリームに入出力する方法では、入出力先をそれぞれ一つしか同時にオープンする事が出来ません。
AZ-Prologでは、ストリームに識別値(ストリーム値)を付けてダイレクトに指定して入出力できる組込述語があります。
ここではその概略を説明します。
まず、ストリームの指定はそれを識別する為のストリーム値という特別の項(Prologのデータ)を使います。
この値をストリーム指定入出力述語(「read/2」「get0/2」「get0/2」「listing/2」「write/2」「writeq/2」「put/2」「seek/2」)の引数で指定すると、カレント入出力ストリームを切換える事なしに(無関係に)指定されたストリームに対して入出力できます。
ストリーム値は、具体的にはファイルオープン述語(「see/2」「tell/2」「tella/2」)によってファイルを入出力オープンすると、それらの述語の第2引数に返される値です。
なお、これらの述語によってオープンされたファイルは、入出力完了後には必ず対応するファイルクローズ述語(「seen/1」「told/1」)でクローズしなければなりません。
プログラム領域をAZ-Prologでは「ヒープ領域」と呼んでいます。
ヒープ領域では、プログラムは処理しやすいように中間言語で格納されています。
ソースプログラムは、コンサル卜時にはシンタックスチェックされ、中間言語に変換されて、ヒープ領域に読み込まれます。
このため、大きなプログラムをコンサル卜するにはかなりの時間を要します。
それに対して、ここで説明する保存方法はヒープ領域の格納イメージをそのままバイナリファイルに保存(save)し、復元(restore)する時もそのまま読み込みますので、比較的早く読み込む事ができます。
AZ-Prologでは、ヒープ領域の格納イメージをバイナリファイルに保存する事を「セーブ(save)」と呼んでいます。
具体的には「save/1」という述語を実行します。
例えば、現在のヒープ領域の格納イメージを「 'heap.bin' 」というバイナリファイルにセーブするには、
| ?-save('heap.bin').↓ yes | ?-
とします。
このときのファイルの拡張子は機能上何でも構いませんが、便宜上「'.bin'」という拡張子を付けるようにする事をお薦めします。
セーブされたバイナリファイルをヒープ領域に読み込んで復元することを「リストア(restore)」と呼びます。
具体的には「restore/1」という述語を実行します。
リストアすると、ヒープ領域の内容がバイナリファイルの内容(セーブしたときのヒープ領域の内容)とそっくり入替わります。
例えば、前項でセーブした「 'heap.bin' 」というバイナリファイルをリストアするには
| ?-restore('heap.bin').↓ top_level | ?-
とします。
この例の様に「restore/1」を実行すると、強制的にインタプリタのトップレベルに戻ります。
したがって、この述語の呼び出しがプログラム中にあると、これを実行した時点でプログラムの実行を中断してトップレベルに戻ります。
プログラム中で「restore/1」を呼び出し、かつ、そのプログラムの実行を中断せずに継続させる事は普通には無理ですが、「d_keyback/1」を使って、「restore/1」に続いて実行すべき処理に相当する質問(起動コマンド)の文字列をキーバッファに入れておくと、「restore/1」が実行されて強制的にインタプリタのトップレベルに戻ったときにその文字列が自動的に入力されて、リストアされたプログラムにある”継続すべき処理”が起動されます。
これにより、見た目にはプログラムは継続されているように見えます。
例えば、あるプログラム中で「 'test.bin' 」を読み込んでその中に登録されている「 go 」述語を実行するには・ ・ ・
…,d_keyback(''go.^ M''),restore('test.bin').
というゴール列を書いておきます。このゴール列を書く場所は特に制限はありませんが、実行時にはこれ以降は決して実行される事はありませんので、このゴール列を書く場所は実質的にプログラムの最後に限定されます。
このゴール列が実行されると、現在カーソルのある行のすぐ下の行から
top_level | ?-go.
と表示され、ゴールgoが実行されることが確認できます。
またAZ-Prologでは、ヒープ領域を十分大きくとれる為、プログラムの実行中にバイナリファイルをヒープ領域に読み込む必要はあまりないでしょう。
AZ-Prologでは、漢字を含む日本語のデータは勿論、プログラム中にもアトムや変数名に使う事ができます。
一般に漢字を表すコードはマルチバイトで表しますが、これを入出力時に1バイトづつに分けて扱うか、マルチバイトをまとめた整数として一括にして扱うかを「漢字モード」で決定します。
マルチバイトをまとめた整数として一括にして扱うモードを「漢字モード = on」、1バイトずつに分けて扱うモードを「漢字モード = off」とします。
漢字モードは、組込述語の「kanji_mode/2 」で設定・変更できます。
デフォルト(インタプリタ起動直後の設定)は「漢字モード = on」です。
具体的な漢字モードの違いは、「name/2 」及び一文字入力系述語から漢字コードが返される場合の値が、「漢字モード = on」のときは漢字一文字分が一つの整数値であり、「漢字モード = off」のときは漢字一文字分が二つの8ビット整数値(Shift-JISの場合。UTF-8では三つか四つ)であるところです。
<例:漢字モードによるname/2 の動作の違い (Windows版Shift-JISの場合)>
| ?-kanji_mode(X,X).↓ <== 「漢字モード= on」である事の確認 X = on↓ yes | ?-name(漢,X).↓ X = [35519]↓ <== 漢字一文字分の文字コードが一つのまとめた整数値で返される yes | ?-name(X,[35519]).↓ X = 漢↓ yes | ?-kanji_mode(X,off).↓<== 「漢字モード= off」に切換える X = on↓ yes | ?-name(漢,X).↓ X = [138,191]↓ <== 漢字一文字分の文字コードが2つの8ビット整数値で返される yes | ?-
もう一つ、漢字モードに関係して注意が必要な事があります。
それはエディタバッファをデータバッファとしてファイルの様に使った場合です。
エディタバッファに「copy0/0」述語(AzEditのCTRL-X CTRL-Vコマンドも同じ)を使ってデータを書き込んで、一文字入力系述語で読み出す場合の漢字コードの扱いは現在の漢字モードに依らず、「copy0/0」述語を使ってデータを書き込んだ時点での漢字モードによって決定されます。
例えば、「漢字モード = on」の状態でエディタバッファに「copy0/0」述語を使ってデータを書き込んだ場合は、現在の漢字モードに関わらず一文字分の漢字コードが一つの整数値で返されます。
逆に「漢字モード= off」の状態でエディタバッファに「copy0/0」述語を使ってデータを書き込んだ場合は、現在の漢字モードに関わらず、一文字分の漢字コードが複数の8ビット整数値で返されます。
通常は「漢字モード = on」の状態のままで何ら差し支えありません。
AZ-Prologのインタプリタは、コンソール出力の記録をファイルにとる機能を持っています。
「log/0」を実行すると、それ以後のコンソールに対する出力がカレントディレクトリの「PROLOG.LOG」というファイルに付け加えられます。(これを「ログをとる」または「ロギング」と呼びます。)
また「log(ファイル名)」を実行すると指定されたファイルにログをとります。
<例>
| ?-log('a.log').↓ yes | ?-write(abcdefg).↓ abcdefg yes | ?-nolog.↓ yes | ?-halt.↓ C:¥azpc> type a.log↓ yes | ?-write(abcdefg). abcdefg yes | ?-nolog. C:¥azpc>
このように、ロギングを停止させるときには「nolog/0」を実行します。
この節では、インタプリタがエラーに対してどのようなメッセージを出力するかを解説します。
コンソールから入力された質問やコマンドに構文エラーがある場合には、コンソールに以下の情報が表示されます。この場合はエラー行は明白です。
Syntax error
Prologソースファイルをコンサルト(またはリコンサルト)したり、コンソールから複数行をまとめて入力する(「5-3-1.コンソールからのプログラム入力」参照)場合は、どのファイルの何行目でエラーが検出されたか、その行の構文解析のどの時点でエラーが検出されたか(行の先頭からその位置までの節情報)が以下の書式で表示されます。
File = | [ファイル名 / 行番号] |
Here!>> | エラーの見つかった節(エラー検出位置まで) |
コンサルトでは複数ファイルを指定でき(:-['ファイル名1,ファイル2,...'].)、コンサルト内部で更に別のファイルをコンサルトする可能性もありますので、これらの情報はエラー箇所を特定する助けになります。
構文エラー(Syntax error)が検出されなかった行(節)、もしくはエラー検出された位置の次の文字から行末までの部分(これも構文エラーがない場合のみ)はヒープ領域に登録されますが、その際に起こり得るエラーは主に以下の2つです。エラー情報表示の書式は構文エラーの場合と同じです。
Illegal clause | 節として認められない。 |
Builtin or C pred exist | 既存の組込述語を再定義しようとした。 |
以下にエラーを含んだ2行だけのPrologソースファイルを読み込む簡単な例を示します(ファイル名をsyntaxerror.plとします)。
a:-b,c d,e.
write(X):- puts(X).
このファイルをAZ-Prologインタプリタからコンサルトしてみます。
| ?-['syntaxerror.pl']. Syntax error File = [syntaxerror.pl / 1] Here!>> a:-b,c d, Builtin or C pred exist File = [syntaxerror.pl / 3] Here!>> write(X):- puts(X). yes | ?-listing. e. yes | ?-
ソースファイルの1行目では、cとdの間にカンマがなく、スペースになっています。dに続くカンマを検出した時点でエラーと認識され、これが「 Here!>> 」以下に表示されています。この行には未だ残りの部分「 e. 」があります。この部分も引き続き構文チェックされますが、問題がないのでヒープ領域にアサートされます。上記の例でlisting/0を実行した時に表示されている「 e. 」はこの部分に該当します。
ソースファイル2行目は、述語write/1を定義しようとしています。構文エラーはないので、そのままアサートしようとしますが、write/1は組込述語なので、エラーになります。
シンタックスエラーもアサート時のエラーも、ピリオドの次の空白文字でエラーを認識するので、表示された行番号よりも前の行にエラー原因がある場合があります。
コンサルトされるファイルの中に「 :- 」や「 ?- 」で始まるゴールの実行が記述されている場合は、実行時に起こり得るあらゆるエラーが起きる可能性があります。
コンサルトで指定されたファイルが存在しない場合は、以下のようなエラーになります(ファイル名を仮にaaa.plとします)。
| ?-['aaa.pl']. Can't see the file ---- Backtrace [aaa.pl] ?-"
本パッケージに付属するエディタAzEditで構文エラーを見つけることもできます。AzEditでソースファイルを読み込んで、そこからコンサルト(またはリコンサルト)した場合に構文エラーを発見すると、コンサルト(またはリコンサルト)を中止してカーソルがその次の文字に移動します。
また、AZ-Prologコンパイラ(azpc)で構文をチェックすることもできます。
実行時にエラーが発生した場合、インタプリタはエラーメッセージを表示した後、エラーを起こしたゴールがどのようにして呼び出されたのかを表示します。(エラーが起きたゴールが、どのようにして呼び出されたのかを逆に辿ることをバックトレースといいます。)
それによってエラーが起こった原因を推測することができます。
この時に表示される情報量は、次項で説明する「エラーモード」によって設定されます。
通常、エラーが起きた場合(「errorset/2」によってエラーがトラップされないとき)、インタプリタはプログラムの実行を強制的に終了し、質問を発したトップレベルまで戻します。それまでオープンされていたファイルはすべてクローズされます。
ただし、「errorset/2」によってトラップされた場合は、ファイルはクローズされません。
<例>
| ?-listing.↓ a(X) :- b(X). a(X). b(X) :- Y is X+1, write(Y). yes | ?-a(a).↓ Illegal Arithmetic expression ---- Backtrace is(_6,+(a,1)) b(a) a(a) ?- | ?-
最後の「 ?- 」はインタプリタが質問によって起動された事を示します。
エラーモードとは、エラーが起きた時の画面に出力される情報量を設定するモードです。
つまり、デバッグ時には出来るだけ多くの情報が要求され、反対にアプリケーション実行時には処理系からの(我がままな)メッセージによって画面が破壊されてしまい、かえって不都合な場合もあります。
このように、時と場合によってエラーに関する情報の量を変化させるのがエラーモードです。
エラーモードは組込述語「errormode/2 」を使って数値で指定します。それぞれのモードによって次の様な差があります。
0 | なにも出力しません。 |
1 | エラーメッセージのみを出力します。 |
2 | エラーメッセージを出力した上でバックトレースし、今まで実行された述語をさかのぼって表示します。 |
3 | デバッグモードがONのとき、エラーメッセージを出力した上でバックトレースし、今まで実行された節を遡ってそのレベル数と共に表示します。 |
前項の例を使って試してみましょう。
<例>
| ?-errormode(_,0).↓ <== 「エラーモード= 0」に切換える _0 = 2↓ yes | ?-a(a).↓ | ?-errormode(_,1).↓ <== 「エラーモード= 1」に切換える _0 = 0↓ yes | ?-a(a).↓ Illegal Arithmetic expression | ?-errormode(_,2).↓ <== 「エラーモード= 2」に切換える _0 = 1↓ yes | ?-a(a).↓ Illegal Arithmetic expression ---- Backtrace is(_14,+(a,1)) b(a) a(a) ?- | ?-errormode(_,3).↓ <== 「エラーモード= 3」に切換える _0 = 2↓ yes | ?-a(a).↓ Illegal Arithmetic expression ---- Backtrace is(_14,+(a,1)) b(a) a(a) ?- <== 「エラーモード= 2」と同じ実行結果となる | ?-debug.↓ <== 「デバッグモード」に切換える yes debug mode on ||?-a(a).↓ Illegal Arithmetic expression ---- Backtrace *[0] Y_16 is a+1 [1] b(a) :- Y_16 is a+1, write(Y_16). [2] a(a) :- b(a). [3] ?- a(a). LOOP = 3 ||?-
AZ-Prologのエラーメッセージは、処理系内部に持っている訳ではなくエラーメッセージファイル「errmsg.db」に格納されています。
このファイルはprologのランダム(アクセス)ファイルです。
何等かのエラーが発生するとインタプリタは、エラーメッセージファイルを探して、そのエラー番号で検索し該当のメッセージを取得してコンソールに表示します。
従って、このエラーメッセージファイルが存在しないとメッセージを取得できない為、代わりにエラー番号をコンソールに表示します。
このファイルは、AZ-Prologのインストール・ディレクトリ下の「doc」というディレクトリにあります。
Linux版またはMac版では少し階層が深いですが、${AZProlog}/share/doc/azprolog/errmsg.dbがそれです。
<例:前々項と同じプログラムで、エラーメッセージファイルが存在しない場合>
| ?-a(a).↓ ERROR NO.26 ---- Backtrace is(_6,+(a,1)) b(a) a(a) ?- | ?-
エラーメッセージの代りにエラー番号がコンソールに表示される。
また、エラーメッセージファイルの内容を変更する事によって、エラーメッセージも変更が可能です。
例えば、エラーメッセージを日本語など英語以外の言語で表示する事も、エラーメッセージファイルの内容を変更するだけでできます。エラーメッセージファイルの内容変更は、AZ-Prologのインストール・ディレクトリ下の「system/pl」ディレクトリの「error.pl」で行ないます。
AZ-Prolog の述語は、大きく分けて「組込述語」と「定義述語」があります。 以下ではそれらを更に細かく分類して説明します。
「組込述語」は、実行プログラム(インタプリタ等)にあらかじめ組み込まれた述語です。
組込述語には、以下のように分類される三つの種類があります。
予め処理系が用意した組込述語 | →「標準組込述語」 |
Prologのプログラムをコンパイルして追加した述語 | →「コンパイル組込述語」 |
C言語のプログラムをコンパイルして追加した述語 | →「Cリンク組込述語」 |
ただし、これ以降単に「組込述語」と記載されている場合は、「標準組込述語」を指すものとします。
標準組込述語の使い方は、マニュアルの「述語リファレンス」を、コンパイル組込述語の作り方については「7.コンパイラ」を、「Cリンク組込述語」の作り方(C言語のプログラムとのリンク方法)については「8.C言語インターフェース」をそれぞれ参照してください。
AZ-Prologでは定義述語をさらに「システム述語」と「ユーザ述語」の2つに分けて扱います。
定義述語は、普通listingによってプログラムのリストを表示したときに出力されます(「組込述語」はその種類に関わらず出力されません)。しかし、既に多くのプログラムで共通に使われ安定した述語ライブラリなどは、listingをとったときに出力されない方が望ましいでしょう。
AZ-Prologではこのような述語を「システム述語」として登録しておくとlistingによって出力されなくなります。「システム述語」に対して、通常listingによって出力される述語を「ユーザ述語」と呼びます。
このシステム述語を定義するモードを「システムモード」と呼びます。「システムモード」に対して通常のモードを「ユーザモード」と呼びます。
逆に言えば「ユーザモード」で入力・定義された述語は「ユーザ述語」であり、「システムモード」で入力・定義された述語が「システム述語」になります。
なお、前述した通り、通常モード(ユーザモード)では「システム述語」はlistingによって出力されませんが、「システムモード」に入ると「システム述語」はlistingによって出力され、逆に「ユーザ述語」は出力されなくなります。
また、システムモードで定義された述語(システム述語)は「s_new/0」を実行しても削除されません。この性質を利用して、次のような使い方が考えられます。例えば「プログラムをすべてシステム述語にしておいて、データ(知識)をユーザ側に入れておくと、一連の動作が終了した後で“s_new/0”を実行するだけで間違い無くデータを削除する」など。
[ユーザモード] [システムモード]の切換えは「s_mode/2」を実行します。
| ?-s_mode(_,on).↓ _0 = off↓ yes ! ?-
そうするとプロンプトが変わってシステムモードになったことを表示します。
逆に、ユーザモードへ戻すには「s_mode(_,off)」を実行します。
! ?-s_mode(_,off). _0 = on yes | ?-
[同名/同アリティ]の異種述語は、システムモードとユーザモード間で共存出来ません。
つまりユーザモードにおいて、既に登録されているシステム述語と同じ述語名と同じアリティの頭部を持つ節を入力しようとするとエラーとなります。
逆にシステムモードにおいて、既に登録されているユーザ述語と同じ述語名と同じアリティの頭部を持つ節を入力しようとしてもエラーとなります。
また、これらのモードに関係なく、組込述語と同じ述語名と同じアリティの頭部を持つ節を入力しようとしてもエラーとなります。
まとめて言えば、既に登録されている述語と違う種類の同じ述語名と同じアリティの頭部を持つ節を入力しようとするとエラーになります。