LoginSignup
0
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-03-25

はじめに

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0