非単バイト文字列の解析の詳細
前回示した例のうち、
$ echo -n "((@@出鼻)を挫く" | iconv -t Shift_JIS | ./parser | iconv -f Shift_JIS marked-string [出呆挫] string [ュ]
が、文字は化けているもののmarked-stringとstringの連接という構造を正しく抽出していることが気になった。
解析そのものに失敗しても不思議はないからだ。
例えば、シフトJISでエンコードされたこの入力の2バイトコードを適当なアルファベットに置き換えてみると、
$ echo -n "((@@abc@)defghi" | iconv -t Shift_JIS | ./parser marked-string [abcefghi] syntax error
「鼻」の2バイト目は'@'
なので「鼻」は「c@」で表している。
すると、「@)d」はマーク終了文字の変更シーケンスとして解釈されるため、
マークされた文字列の語頭として「abcefghi」が抽出された後、
マーク終了文字の'd'
が現れる前に入力が尽きて構文エラーとなる。
なぜ、これと同じことが「((@@出鼻)を挫く」では起きなかったのだろう。
シフトJISで「((@@出鼻)を挫く」のバイト列は16進数2桁で表すと、
28 28 40 40 8f 6f 95 40 29 82 f0 8d c1 82 ad
の15バイト長になる。分解してみると、
28 28 40 ((@ マーク開始文字を'@'に設定 40 @ マーク付け開始 8f 6f 95 マーク付けされた文字列の一部 40 29 82 @) マーク終了文字を'\x82'に設定 f0 8d c1 マーク付けされた文字列の一部 82 マーク付け終了 ad マーク外の文字列
なんと、文字「く」の1バイト目が変更後のマーク終了文字'\x82'
に一致していた。
この偶然が6バイト3文字長のmarked-stringと1バイト1文字長のstringの連接という解釈を生んだのだった。
ちなみに、マーク終了文字を変更した「()@(出鼻@を挫く」の場合は、
28 29 40 28 8f 6f 95 40 40 82 f0 8d c1 82 ad
中ほどのマーク終了文字の連続「40 40」で構文エラーが発生する。