はじめに
swi-prologでtcpサーバーを作る必要があって、送受信はできるのだが、マルチバイトの文字を送受信する時に、文字列にならない。socketのストリームからは、バイトシーケンスのリストで受け取るが、asciiの場合は、自動的に文字に変換する(というか、utf8のバイトシーケンスとprologの自動処理するコードポイントが一致しているからで、特に変換しているのではないのだろう)。マルチバイトだとダメなのだ。
swi-prologのmanualを1日にらめっこしていたが、うまく変換する手続きが見つからない(あると思うのだが・・・・)。utf8コードをコードポイント列にする宣言文を用意しようかと思ったが、7000行以上になるのでやめた。
結局、変換する手続きをprologで書くことになった。以下がその結果である。
swi-prologプログラム
符号化のロジックについては、UTF-8の符号化方法についてを参照させていただいた。
prologでビット演算しなければならないので、二の足を踏んだが、意外と簡単だった。実質9行で済むというのもprologらしいのかもしれない。
(注)utf8の1バイト文字と3バイト文字しか対応させていない。
%%
%% utf8のbyte sequenceをprolog内の文字列に変換する
%%
%% (例)
%% 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]
%% utf8のバイトシーケンスを文字列に変換する
%% asciiが終わってマルチバイトも調べないために、最後にカットを入れている
%% 何れにしても、ここでバックトラックは不要なのでつけておく
%% 途中経過のコードポイントリストも出力する
getstring(L,X) :- getcodepoint(L,Y),write(Y),atom_codes(X,Y),!.
%% 再帰の終了条件
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.
実行結果
プログラムの冒頭コメントにある例などを実行させてみた。
?- ['codepoint.swi'].
true.
?- getstring([108, 105, 111, 110],X).
[108,105,111,110]
X = lion.
?- getstring([227,131,169,227,130,164,227,130,170,227,131,179],X).
[12521,12452,12458,12531]
X = ライオン.
?- getstring([227,131,169,108,227,130,164,105,227,130,170,111,227,131,179,110],X).
[12521,108,12452,105,12458,111,12531,110]
X = ラlイiオoンn.
?- getstring([228,187,138,230,151,165,227,129,175,232,137,175,227,129,132,229,164,169,230,176,151,227,129,167,227,129,153,227,129,173],X).
[20170,26085,12399,33391,12356,22825,27671,12391,12377,12397]
X = 今日は良い天気ですね.
?-
試していただければ幸いである。
サーバーから送信する時に、逆変換が必要になるので、上記プログラムを双方向に改良しなければならない。(その後作成した双方向のプログラムはブログ記事「知識をクライアントに送付するprologサーバー」の中に記載した)