KAKASIのコードについて

Pocket

ここしばらくKAKASIのソースをいじっています。いい加減あたらしいバージョンをリリースして、しばらくは他のことをやりたいのですが、次に戻ってきたときいろいろと忘れそうなので、記録として残しておこうと思います。

そもそもKAKASIとは何か

オリジナルのKAKASIはたかはしもとのぶさんによって作成されたソフトウェアです。漢字、カタカナ、ひらがな、ローマ字を相互に変換する機能をもっています。SKKの辞書を用いて、

オリジナルのKAKASIに、単語を分割する「分かち書き」機能をパッチとして作成したのが、馬場さんです。馬場さんはこの機能を利用して、freeWAISやNamazuなどの全文検索ソフトウェアで日本語を扱えるようにしました。

自分もNamazuの開発にかかわる中で、パッチとしてKAKASIの機能をメンテナンスするのはしんどい、KAKASIのリリース自体長らく行われていない、といった理由で、Namazuの開発の一環としてKAKASIの開発を継承することをたかはしさんに打診し、受け入れられて今に至ります。

最近の作業

KAKASIのバージョンは長いこと2.3.4でリリースが止まっていました。2006年ごろ自分がリリースをしようとした痕跡があるのですが、その後Debian方面でlibtext-kakasi-perlのテストが通らなくなった等いろいろな要因で「これはリリースしないといけない」と思い、ようやく出せたのが2014/1/18です。このリリースで一番大きい変化はUTF-8のサポートです。といっても、iconvに依存しています。KAKASI本体がISO-2022-JP, EUC-JP, SJIS(cp932)の変換機能を持っているのですが、UTF-8対応のためにテーブルを持つのもどうかな、と思いiconvを使うようにして実装しました。

これでひとまず解決か、と思いきやperlモジュールでテストが通らない問題が直っていませんでした。何が起きていたかというと、分かち書きオプション-wを付加したときに、先頭に余計な空白を出力するために、期待されていた出力とは異なっていたのです。

このバグはDebianパッケージの自動ビルドで発覚しました。開発者がアップロードしたアーキテクチャ以外(mips, arm等)のパッケージは、自動的にビルドサーバーによってパッケージが作成されます。その時make testまで実行するので、ライブラリの変化によってバグが顕著化されました。

これを直すために作業を進めていたところ、さらに複数のバグが発覚し、それらの対応も必要になり、今に至ります。

KAKASIの基本データ構造Character

kakasi.hで様々なデータ構造が定義されていますが、文字を格納する基本となる型Character(実体はstruct character)が入出力の基本となります。

typedef struct character {
    char type;
    unsigned char c1, c2;
} Character;

typeは文字の種別を表し、0~5, 127の値をとります。値の定義は同様にkakasi.hで行われています

/* character set */
#define ASCII    0
#define JISROMAN 1
#define GRAPHIC  2
#define KATAKANA 3
#define JIS78    4
#define JIS83    5
#define OTHER  127

typeの値は常に正しい値が入っているわけではなく、マルチバイト文字が入力された直後はとりあえずOTHERを設定し、のちの処理に応じて適切な値に変更します。ASCIIとJISROMANはISO-2022-JP向けの値です。SI/SOによって、単なるASCIIと区別してJIS X 0212ラテン文字集合を選択できるのがISO-2022-JPの特徴です。主な違いは0x5Cが半角の”¥”(円記号)として定義されている点(ASCIIならバックスラッシュ)です。

Character型の変数は、多くの場面で入力バッファと出力バッファを兼ねています。なのでソースコードの可読性がよろしくありません。まだコンピューター資源が乏しかったころに書かれたコードなのである意味仕方がないともいえます。

内部的な基本となるコードはISO-2022-JPです。制御コードを抜いたMSBが0のデータ(0x00~0x7f)をc1, c2に保持します。出力時にコードの加工をします(kanjiio.c putkanji参照)。

文字変換処理

文字種の相互変換処理は、大域変数proc[]に関数へのポインタとして呼び出すべき関数を保持させ、それらを呼び出すようになっています。

static int (*proc[8])()={NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
/* ASCII, JISROMAN, KATAKANA, GRAPHIC, ZENKAKU-KIGOU, ZENKAKU-KATAKANA, ZENKAKU-HIRAGANA, KANJI, */

個々の処理は*2.cというファイル名のソースに保持されています。たとえば全角カタカナからの変換であればkk2.cが対応するソースになります。

ローマ字変換一般的にローマ字にはヘボン式と訓令式の2種類があるとされています。実際にはもう少し複雑なのですが、オリジナルのKAKASIはそれらをサポートしているといいつつ、一般的な表記とも微妙に異なる体系を持っていました。このあたりは最近のコミットでもう少ししっかりと明文化されたmodified hepburnと呼ばれる体系と、ISO3602、BS4812に対応させています。

ローマ字のテーブルはソースコード埋め込みで、メンテナンス性がよくありません。しかもカタカナ、ひらがなそれぞれにテーブルを持っています(kk2.c, hh2.c)。メンテナンスの手間を低減するため、2進数で書かれたソースをEUC-JPに変換するdumptable.pl, その逆を行うocttable.pl, テーブルの文字列をコード順に並び替えるromantablesort.plの3つのスクリプトを書きました。これで多少メンテナンス性が向上しました。

これだけ書いておけば次の作業の時には思い出すのにそれほど苦労しないだろうと思います。他に記録を残しておかないといけないことを思い出したら、またblogに書いておこうと思います。

By knok

I am a Debian Developer and a board member of Free Software Initiative (FSIJ).