Prolog
UTF-8
Sequence
Byte
codepoint

swi-prologで、utf-8のバイトシーケンスをコードポイントリストに変換し文字列にする


はじめに

swi-prologでtcpサーバーを作る必要があって、送受信はできるのだが、マルチバイトの文字を送受信する時に、文字列にならない。socketのストリームからは、バイトシーケンスのリストで受け取るが、asciiの場合は、自動的に文字に変換する(というか、utf8のバイトシーケンスとprologの自動処理するコードポイントが一致しているからで、特に変換しているのではないのだろう)。マルチバイトだとダメなのだ。

swi-prologのmanualを1日にらめっこしていたが、うまく変換する手続きが見つからない(あると思うのだが・・・・)。utf8コードをコードポイント列にする宣言文を用意しようかと思ったが、7000行以上になるのでやめた。

結局、変換する手続きをprologで書くことになった。以下がその結果である。


swi-prologプログラム

符号化のロジックについては、UTF-8の符号化方法についてを参照させていただいた。

prologでビット演算しなければならないので、二の足を踏んだが、意外と簡単だった。実質9行で済むというのもprologらしいのかもしれない。

(注)utf8の1バイト文字と3バイト文字しか対応させていない。


codepoint.swi

%%

%% 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サーバー」の中に記載した)