prologで書いた、日本語wikipediaのデータを他から参照するために、prologでサーバーを用意するのが最も便利だと判断している。
そのprologサーバーを動かそうとしているのだが、文字コードのところでつまずいて週末を悶々としていた。結局、なんとか切り分けた問題は、次のようなものだ(これにも時間がかかった)。
例えば、クライアントからある文字列をutf8で送ったとすると、swi-prologのサーバーは、それをバイト列のストリームで受け取る。しかし、prologの内部では、アスキー文字は、そのストリームを処理して文字列に自動変換するのだが、日本語などのマルチバイト文字は変換しないので、日本語の宣言文などとユニフィケーションさせても一致すべきものが一致しなくなるという厄介なことが発生するのだ。
ただ、atom_codesという関数を使えば、文字列とUnicodeのコードポイントの相互変換は可能である。つまり、次のような感じである。
?- atom_codes(ライオン,X). X = [12521, 12452, 12458, 12531]. ?- atom_codes(X,[12521, 12452, 12458, 12531]). X = ライオン.
ここで、数字のリストは、コードポイントのリストである。
クライアントからprologのサーバーに「ライオン」という文字列を送ると、次のようなリストとして受け取る。
[227,131,169,227,130,164,227,130,170,227,131,179]
227から始まる、3バイト分が一文字になっている。この文字列を先のコードポイントに変換できれば、文字列になるのだ。3バイトを16進に変換するのは、
?- hex_bytes(X,[227,131,169]). X = e383a9.
で、できるので、最初は、このutf8コードとコードポイントの相互変換データをprologに組み込んで、変換することを考えたが、7000行以上の宣言文を咥えこまなければならないので、とても負担感がある。そこで、数量的変換のアルゴリズムが、こちらに解説されていたので、それを元に、変換のための規則を作ってみた。
utf8コードの3バイトものだけだが、次のように簡単になる。nth1はインデクス番号の要素を取り出す組込述語。
%% 00001111 -> 15 %% 00111111 -> 63 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.
実行結果は次のようになる。
?- utf8iso([227,131,169],X). % ラ X = 12521 ?- utf8iso([227,130,164],X). % イ X = 12452
あと、より完全なものにするためには、半角アスキーコードと3バイト文字列を識別するようになればいい。
その完成バージョンをQiitaに投稿した。
「swi-prologで、utf-8のバイトシーケンスをコードポイントリストに変換し文字列にする」