unpackと\xNNエスケープシーケンス
次のコードは(255)が返ってくると思ったんだけど、(195)が返ってきた。
$ gosh -u binary.pack gosh> (unpack "C" :from-string "\xff") (195) gosh>
文字列をユニフォームベクタに変換してみると、やはり195と191になっている。
gosh> (use gauche.uvector) #<undef> gosh> (string->u8vector "\xff") #u8(195 191) gosh>
なぜだろう。リーダーが文字列を読んでいるのはsrc/read.cのread_stringだ。
static ScmObj read_string(ScmPort *port, int incompletep, ScmReadContext *ctx) { int c = 0; ScmDString ds; Scm_DStringInit(&ds); #define FETCH(var) \ if (incompletep) { var = Scm_GetbUnsafe(port); } \ else { var = Scm_GetcUnsafe(port); } #define ACCUMULATE(var) \ if (incompletep) { SCM_DSTRING_PUTB(&ds, var); } \ else { SCM_DSTRING_PUTC(&ds, var); } #define INTRALINE_WS(var) \ ((var)==' ' || (var)=='\t' || SCM_CHAR_EXTRA_WHITESPACE_INTRALINE(var)) /* 中略 */ case 'x': { int cc = read_string_xdigits(port, 2, 'x', incompletep); ACCUMULATE(cc); break; }src/read.c - read_string
read_string_xdigitsで"x"以降の2バイトを16進数として読んでいるから、ccは255となって、それをACCUMULATEマクロに渡している。ここでリーダーが読んでいるのは不完全文字列ではないから、SCM_DSTRING_PUTCが呼ばれる。これはsrc/gauche/string.hで定義されたマクロで、ScmDString(文字列をチャンクのリストとして管理しているデータ構造。たぶん)に文字をひとつ追加する。
#define SCM_DSTRING_PUTC(dstr, ch) \ do { \ ScmChar ch_DSTR = (ch); \ ScmDString *d_DSTR = (dstr); \ int siz_DSTR = SCM_CHAR_NBYTES(ch_DSTR); \ if (d_DSTR->current + siz_DSTR > d_DSTR->end) \ Scm__DStringRealloc(d_DSTR, siz_DSTR); \ SCM_CHAR_PUT(d_DSTR->current, ch_DSTR); \ d_DSTR->current += siz_DSTR; \ if (d_DSTR->length >= 0) d_DSTR->length++; \ } while (0)src/gauche/string.h - SCM_DSTRING_PUTC
この中で使われているSCM_CHAR_PUTマクロは、コンパイル時に指定した内部エンコーディングがUTF-8ならsrc/gauche/char_utf_8.hに定義されたものが使われる。
#define SCM_CHAR_PUT(cp, ch) \ do { \ if (ch >= 0x80) { \ Scm_CharUtf8Putc((unsigned char*)cp, ch); \ } else { \ *(cp) = (unsigned char)(ch); \ } \ } while (0)src/gauche/char_utf_8.h - SCM_CHAR_PUT
ここでchは255(ff)だから、Scm_CharUtf8Putcが呼ばれる。
void Scm_CharUtf8Putc(unsigned char *cp, ScmChar ch) { if (ch < 0x80) { *cp = (u_char)ch; } else if (ch < 0x800) { *cp++ = (u_char)((ch>>6)&0x1f) | 0xc0; *cp = (u_char)(ch&0x3f) | 0x80; } else if (ch < 0x10000) {src/gauche/char_utf_8.h - Scm_CharUtf8Putc
0x80 < ch < 0x800だから2番目の条件式が成立して、chは2バイトを使ってUTF-8エンコードされる。UTF-8 - Wikipediaによると、2バイトでエンコードする場合は、1バイト目の上位3ビットと2バイト目の上位2ビットがフラグとして使われて、残りの11ビットにchのビットパターンが右詰めでセットされる。上記のコードはそれをやっていて、その結果エンコードされた2バイトのビットパターンは
11000011 10111111
となり、これを10進数で表せば
gosh> #b11000011 195 gosh> #b10111111 191
だから、冒頭のコードは"\xff"をUTF-8エンコードした結果の1バイト目を返していたということだった。
ここまで書いて気づいたけど、リファレンスにちゃんと書いてあったorz
\xNN 2桁の16進数NNで指定されるバイト。このバイトは内部エンコーディングによって解釈されます。Gauche ユーザリファレンス: 6.11 文字列