GNU Prolog に UTF-8 サポートを追加した話

## きっかけ

SWI-Prolog では Prolog コードに UTF-8 で記述することができます。
特に SWI-Prolog に不満があるわけではありませんが、GNU Prolog は WAM コードの出力とか面白い機能があります。

また、異なる処理系上で同じソースコードを持っていって動かしてみたら挙動が違っていた、仕様を調べてみるとソースコードに不備が見つかった、などということもあります。

GNU Prolog でも UTF-8 が使えたら良いと思いました。

## UTF-8 が使える GNU Prolog について

当方が UTF-8 サポートを追加した GNU Prolog を公開しています。
適当なディレクトリで以下のような git コマンドを実行するとソースコード一式をダウンロードできます。

```
git clone https://github.com/tadashi9e/gprolog-utf8
```

gprolog-utf8 というディレクトリができます。

元となっている GNU Prolog のソースコードと同じ手順でビルドできます。INSTALL ファイルにあるとおりですが、一応ビルド手順を書きます。

### gprolog-utf8 のインストール手順

clone してきた gprolog-utf8 の下に src ディレクトリがあるので、そのディレクトリに移動します。

```shell
cd gprolog-utf8/src
```

autoconf, configure, make の順に実行してソースコードをコンパイルします（autoconf がない場合は別途インストールしてください）。

```shell
autoconf
./configure
make
```

ビルドできたらインストールします。

```shell
sudo make install-system install-links
```

/usr/local/bin/gprolog として実行できるようになります。
インストール先を変えたい場合には INSTALL ファイルを参照し、configure に適当なオプションを付けると可能です。

#### Docker でコンテナとして動かす

Docker で動かしたい、という人がいるかどうかわかりませんが、Dockerfile を書いてみました。

```dockerfile
FROM alpine:3.11.3
RUN apk --update add autoconf git gcc libc-dev make && \
    git clone https://github.com/tadashi9e/gprolog-utf8 && \
    (cd gprolog-utf8/src && autoconf && ./configure && make && \
     make install-system install-links) && \
    rm -rf gprolog-utf8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
```

調子に乗って Docker Hub に push してみました。以下のように pull して実行することもできます。

```shell-session
$ docker pull tadashi9e/gprolog-utf8
$ docker run -it tadashi9e/gprolog-utf8 /usr/local/bin/gprolog
GNU Prolog 1.4.5 (64 bits)
Compiled Mar  2 2020, 10:50:25 with gcc
By Daniel Diaz
Copyright (C) 1999-2015 Daniel Diaz
| ?- atom_concat('東京', 'オリンピック', X).

X = 東京オリンピック

yes
| ?- 
```

## GNU Prolog への UTF-8 サポート追加のためにやったこと

あとから思い出して書いているので漏れがあるかもしれません。

#### 組み込み述語への UTF-8 サポートの追加

アトムを扱う組み込み述語への UTF-8 サポートの追加。

atom_concat/3, get_char/1, get_char/2, get_code/1, get_code/2, get_key/1, get_key/2, get_key_no_echo/1, get_key_no_echo/2, peek_char/1 peek_char/2, peek_code/1, peek_code/2, unget_char/1 unget_char/2, unget_code/1, unget_code/2

#### ソースコード読み込み処理への UTF-8 サポートの追加

pl2wam, wam2ma が UTF-8 ソースコードを扱えるように修正。

#### コンソール処理への UTF-8 サポートの追加

コンソール(linedit) での UTF-8 入出力。
ヒストリ表示、カーソル移動は結構大変でした。UTF-8 文字が画面上でどのような幅として表示されるのかわからないので、一文字単位の前後移動は行頭から移動の度に表示しなおす、バックスペースやデリートの場合には一行まるごと削除して行頭から表示しなおす、というふうにエスケープシーケンス処理を再表示に書き換えています。
それでもヒストリ上の過去・未来への行き来を繰り返すとタイミング依存で表示がおかしくなることがありますが、実用上大きな問題ではないので放置しています。

#### シングルトンチェックに関する変更

ワイドキャラクタ文字はすべて小文字扱いにしています。ワイドキャラクタ文字から始まる文字列はアトムとし、'_' にワイドキャラクタ文字が続く文字列は変数名として扱えばよいだろう、という判断です。

一方で GNU Prolog においては、'_' で始まる変数はシングルトンチェック対象外です。

しかし、変数名に日本語を使ったソースコードを書こうとすると、以下のように '_変数名' のような形式の変数を大量に書くことになります。

```prolog

推論過程表示(_前提, _推論過程) :-
    式一覧の表示(_前提), nl,
    推論過程表示(_推論過程).
推論過程表示([]).
推論過程表示([_推論|_残りの推論]) :-
    _推論 =.. [ _推論ルール名, _前提, _結論, _次の式一覧],
    write('---------------- '), write(_推論ルール名), write(' ('),
    式一覧の表示(_前提), write(' ⊢ '), 式一覧の表示(_結論), write(' )'), nl,
    式一覧の表示(_次の式覧), nl,  % ←　うっかり一文字消してしまった！
    推論過程表示(_残りの推論).
```

シングルトンチェックの有無は動作に影響しないのでどうでもいいといえばどうでもいいことなのですが、Prolog ソースコードのタイポはバックトラックも絡んで非常にわかりにくい挙動を引き起こすことが多いのでシングルトンチェックがないのはつらいです。

GNU Prolog のパーサに手を入れて、'_' で始まる変数の場合であってもワイドキャラクタ文字が続く場合には特例としてシングルトンチェックするようにしてしまいました。

```
$ gprolog
GNU Prolog 1.4.5 (64 bits)
Compiled Mar  1 2020, 05:31:13 with gcc
By Daniel Diaz
Copyright (C) 1999-2015 Daniel Diaz
| ?- [p].
compiling /tmp/p.pl for byte code...
/tmp/p.pl:208-213: warning: singleton variables [_次の式一覧,_次の式覧] for 推論過程表示/1
/tmp/p.pl compiled, 321 lines read - 30396 bytes written, 14 ms

(5 ms) yes
```

SWI-Prolog も同様にシングルトンチェックが効くので、SWI-Prolog に寄せた、という言い方もできそうです。

```
$ swipl
Welcome to SWI-Prolog (threaded, 64 bits, version 7.6.4)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- [p].
Warning: /tmp/p.pl:208:
	Singleton variables: [_次の式一覧,_次の式覧]
true.
```

## おまけ: Emacs の run-prolog で UTF-8 を扱う方法

SWI-Prolog をお使いの方にも役に立つ情報です。

SWI-Prolog はコンソール上で UTF-8 を出力すると微妙に表示が狂います（すみません。当方の環境だけなのかもしれません）。

![SWI-Prolog の場合](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412308/266203fc-1d6e-58b9-ba84-fcec5fee17e8.png)
![GNU Prolog の場合](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412308/bc9ce89f-1786-6e66-09e7-2de1c3d593a8.png)

emacs の run-prolog で実行すると SWI-Prolog でも UTF-8 を含んだ項をきれいに表示できます。

ただし、デフォルト設定で文字化けする場合には（~/.emacs.d/init.el などの）設定ファイルに以下のような記述をしておく必要があります。

```elisp
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
```

また、run-prolog が SWI-Prolog を起動する場合で、GNU Prolog に切り替えたい場合には以下のように設定すれば可能です。

```elisp

(setq prolog-program-name "/usr/local/bin/gprolog")
```