UTF-8の勉強をした
最近は悶々とPEGパーサジェネレーターを作りたいなぁ、などと考えながらペーパーワークに励んでいる毎日なわけだけども、パーサーを作るならUTF-8は最低でも扱えるようにしておきたいと思い、2時間ほどUTF-8の勉強をしてみた。
まず、UTF-8文字列はどのような規則のバイト列であるのか、WikipediaやRFCを眺めてみたところ、次のような仕様になっていた。
種類 | ビットパターン |
---|---|
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のコードポイントの配列として文字列を表現すればよいらしい、というところまで考えて今日はおしまい。