UTF-8の勉強をした

最近は悶々とPEGパーサジェネレーターを作りたいなぁ、などと考えながらペーパーワークに励んでいる毎日なわけだけども、パーサーを作るならUTF-8は最低でも扱えるようにしておきたいと思い、2時間ほどUTF-8の勉強をしてみた。

まず、UTF-8文字列はどのような規則のバイト列であるのか、WikipediaRFCを眺めてみたところ、次のような仕様になっていた。

種類 ビットパターン
ASCII範囲 0??????? (ASCIIと同じ表現)
2バイト 110????? 10??????
3バイト 1110???? 10?????? 10??????
4バイト 11110??? 10?????? 10?????? 10??????

ビット列の、?の部分のビットをあわせて1つの2進数として読んだものが、UCSのコードポイントになっているらしい。たとえば、日本語の「あ」の場合は 11100011 10000001 10000010 というビット列で、?の部分だけとってくると0011000001000010 = 12354 = 0x3042 となる。実際にUnicodeのコード表をみてみると、ちゃんと「あ」だ。

ということを確認するのと、C言語からUTF-8を扱う初歩の練習としてCで1バイトごとに、UTF-8のデータを出力するようなコードを書いてみた。1??????? ってバイト列は、charだと負の数として認識されてしまうので、unsigned にしてやらなきゃいけなかった。場合分けが汚いが、文字コード関係のプログラムはこんなかんじのコード多いよねーとか思って放置。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char * argv[]){
    if (argc < 2){
        perror("argument required");
        exit(1);
    }
    unsigned char c;
    int i = 0;
    while((c = argv[1][i++]) != '\0'){
        printf("character '0x%x': ", c);
        if (c >> 6 != 2){ // if initial octet
            printf("(init)");
            if (c >> 4 == 0xF){
                printf("0x%x", c & 0x7);
            } else if (c >> 5 == 0x7){
                printf("0x%x", c & 0xF); 
            } else if (c >> 6 == 0x3){
                printf("0x%x", c & 0x1F);
            } else if (c >> 7 == 0){
                printf("0x%x", c & 0x7F);
            } else {
                printf("invalid octed '0x%x'\n", c);
                exit(1);
            }
        } else {
            printf("0x%x", c & 0x3F);
        }
        puts("");
    }
    return 0;
}

その後、マルチバイト文字を内部で保持するにはどうすればいいんかなーとか思って、gaucheソースコードを読む。src/gauche/char_utf_8.h の Scm_CharUtf8Getc を読んでみたところ、charの配列を調べて、UCSのコードポイントを返しているらしい。ScmCharはlongなので、UCSのコードポイントの配列として文字列を表現すればよいらしい、というところまで考えて今日はおしまい。