prologの二分木化された日本語wikipediaの出力は、TCPサーバーでやろうとしているわけだが、そのためにはprologでサーバーを立ち上げなければならない。クライアントとのマルチバイト文字のやりとりで手間取ったが、それもなんとかクリアしたので、現状をここにアップしておく。
まず、prologのTCPサーバーは、swi-prologのマニュアルにあるものを参考に以下のように作成する。
%%%% %% 知識データセクション、簡単な例 %%%% animal(ライオン,肉食動物). animal(キリン,草食動物). %%%% %% TCPサーバーセクション %% 参照: %% http://www.swi-prolog.org/pldoc/man?section=stream-pools %%%% %% home brewでインストールしたswi-prologでは、次のライブラリが読めない可能性がある %% その場合は、ソースからコンパイルし直し、最新バージョンを入れる :- use_module(library(streampool)). server(Port) :- %% tcpソケットの作成 tcp_socket(Socket), %% ソケットをアドレスにつなげる 第二引数は、portのみか、HostPort tcp_bind(Socket, Port), %% ソケットからリクエストを受け取る、第二引数は、ペンディングリクエストの上限 tcp_listen(Socket, 5), %% ソケットとコミュニケーションのためのストリームを作成する tcp_open_socket(Socket, In, _Out), %% Inが利用可能になったら、accept(Socket)が呼び出される add_stream_to_pool(In, accept(Socket)), %% loopが空になるまでdispatch_stream_poolが呼び出される %% dispatch_stream_poolは入力があるとadd_stream_to_poolのGoalを呼び出す stream_pool_main_loop. accept(Socket) :- %% ソケットからのクライアントからのリクエストを待つ tcp_accept(Socket, Slave, Peer), tcp_open_socket(Slave, In, Out), add_stream_to_pool(In, client(In, Out, Peer)). client(In, Out, _Peer) :- %% 入力ストリームから次の行を読み出す 結果はCommandにユニファイされる %% 改行までか、ファイルの終わりまで読み込まれる 改行コードは削除される %% 改行を含むブロックを読み込むときは read_line_to_codes/3 を使う read_line_to_codes(In, AnimalCodes), %% 入力ストリームを閉じる close(In), %% バイトシーケンスを文字列に変換する utf8tring(AnimalCodes,Animal), %% 受け取った文字列の表示 write(Animal),nl, %% 知識のユニフィケーション animal(Animal,Category), %% flush_output(), %% 文字列をバイトシーケンスに変換する utf8tring(CategoryCodes,Category), %% サーバーコンソールへの表示 format('~s ~s ~n', [Animal,Category]), %% フォーマットされた文字を出力ストリームに書き込む %% 与える文字列は、バイトシーケンスのリスト化されたものでなければならない %% ~sはCでいう %s 、 ~n は改行 format(Out, '~s is ~s ~n', [AnimalCodes,CategoryCodes]), %% 出力ストリームを閉じる close(Out), %% プールからストリームを削除する delete_stream_from_pool(In).
冒頭の知識は、超簡単なものだが、日本語wikipediaの場合は、別ファイルになる。
このサーバーで最も困ったのは、ストリームの送受信は、すべてutf8のバイトシーケンスになっていることだった。ascii文字は、そのままでも良いが、マルチバイト文字に対応していない。前の記事で書いた日本語とバイトシーケンスを相互に変換するprolog手続きを作成しなければならなかった。Qittaには、片方向のものをあげたが、双方向に変換できるprologプログラムは、以下のようになる。
%% %% utf8のbyte sequenceをprolog内の文字列に変換する %% 逆に、文字列をutf8のバイトシーケンスに変換する %% %% (例) %% byte sequence %% ライオン => [227,131,169,227,130,164,227,130,170,227,131,179] %% lion => [108, 105, 111, 110] %% アスキーとマルチバイトの混合 %% ラlイiオoンn = [227,131,169,108,227,130,164,105,227,130,170,111,227,131,179,110] %% codepoint %% ライオン => [12521, 12452, 12458, 12531] %% TOPレベルpredicate, 双方向 %% utf8のバイトシーケンスを文字列に変換する %% asciiが終わってマルチバイトも調べないために、最後にカットを入れている %% 何れにしても、ここでバックトラックは不要なのでつけておく %% 途中経過のコードポイントリストも出力する utf8tring(L,X) :- var(X),getcodepoint(L,Y), write(Y),nl,atom_codes(X,Y),!. %% 文字列から、utf8のバイトシーケンスを取得する utf8tring(S,X) :- var(S),atom_codes(X,L), getutf8sequence(L,S),write(S),nl,!. %% 再帰の終了条件 getutf8sequence([],[]). %% コードポイントリスト(L), utf8バイトシーケンス(S) %% アスキー文字の場合 getutf8sequence(L,[A|X]) :- [A|T] = L, A < 256, getutf8sequence(T,X). %% マルチバイトの場合 getutf8sequence(L,Z) :- [M|T] = L, isoutf8(M,X), getutf8sequence(T,Y), append(X,Y,Z). %% マルチバイト文字のコードポイントをutf8の3バイトに変換する isoutf8(X,[C1,C2,C3]) :- C1 is X >> 12 /\ 15 \/ 224, C2 is X >> 6 /\ 63 \/ 128, C3 is X /\ 63 \/ 128. %% 再帰の終了条件 getcodepoint([],[]). %% アスキー文字の場合 getcodepoint(L,[H|X]) :- [H|T] = L ,isascii(H),getcodepoint(T,X). %% utf8マルチバイトの場合 getcodepoint(L,[X1|X2]) :- headthree(L,Y,T), utf8iso(Y,X1),getcodepoint(T,X2). %% マルチバイトの場合、頭の3個を取り出して、残りをTに入れる headthree(L,[X1,X2,X3],T) :- [X1|T1]=L, [X2|T2]=T1,[X3|T]=T2. %% 最高bitが0ならば、アスキー文字 isascii(A) :- (A >> 7) =:= 0. %% 3バイトのutf8コードをコードポイントに変換する %% 00001111 -> 15 %% 00111111 -> 63 %% nth1 はリスト(L)の指定位置(N)の要素を取り出す(X) utf8iso(L,X) :- nth1(1, L, Y1), Z1 is 15 /\ Y1, nth1(2, L, Y2), Z2 is 63 /\ Y2, nth1(3, L, Y3), Z3 is 63 /\ Y3, X is Z1 << 12 \/ Z2 << 6 \/ Z3.
こちらのプログラムもサーバープログラムと同時に読み込まれなければならない。その後に、ポートを決めてサーバーを立ち上げる。
テスト用に、クライアントプログラムをjavaで作成した。以下に参考例と示しておく。TCPに対応するクライアントならば、どのようなものでも良い。telnetでもいけると思う。
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; /** * * @author washida */ public class JPClient { /** * @param args the command line arguments */ public static void main(String[] args) { try { String server = "localhost"; int port = 20000; Socket soc = new Socket(server, port); OutputStream os = soc.getOutputStream(); PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os,"UTF-8"))); //pw.print("ライオン\r\n"); //pw.flush(); pw.print("キリン\r\n"); pw.flush(); InputStream is = soc.getInputStream(); BufferedReader in = new BufferedReader(new InputStreamReader(is)); String line; while((line = in.readLine()) != null){ System.out.println(line); } pw.close(); os.close(); in.close(); is.close(); }catch (IOException e) { System.out.println("Exception: " + e); } } }
実行例は次のようになる。
%% サーバー(prolog)側の端末 ?- ['server.swi','utf8string.swi']. true. ?- server(20000). [12461,12522,12531] キリン [232,141,137,233,163,159,229,139,149,231,137,169] キリン 草食動物 [12521,12452,12458,12531] ライオン [232,130,137,233,163,159,229,139,149,231,137,169] ライオン 肉食動物 // クライアント(java)側の端末 キリン is 草食動物 ライオン is 肉食動物
サーバー側には、変換過程を示すために、バイトシーケンスとコードポイントのリストが表示されている。サーバーは、port番号20000番で開いている。優先的に利用されていないポート番号ならんば、何番でも良い。また、ネットワークにつながっているクライアントであれば、どこからでもprologが処理する知識を獲得できるわけである。