Perl 5.14 の文字クラスの動作をもう少し調べてみた。難しい。
"\x{17f}" =~ /(?u)[[:lower:]]/ → match "\x{17f}" =~ /(?a)[[:lower:]]/ → unmatch ★ "\x{17f}" =~ /(?aa)[[:lower:]]/ → unmatch "\x{17f}" =~ /(?iu)[[:lower:]]/ → match "\x{17f}" =~ /(?ia)[[:lower:]]/ → match ★ "\x{17f}" =~ /(?iaa)[[:lower:]]/ → unmatch "\x{212a}" =~ /(?u)[[:upper:]]/ → match "\x{212a}" =~ /(?a)[[:upper:]]/ → unmatch ★ "\x{212a}" =~ /(?aa)[[:upper:]]/ → unmatch "\x{212a}" =~ /(?iu)[[:upper:]]/ → match "\x{212a}" =~ /(?ia)[[:upper:]]/ → match ★ "\x{212a}" =~ /(?iaa)[[:upper:]]/ → unmatch "\x{df}" =~ /(?u)[[:lower:]]/ → match "\x{df}" =~ /(?a)[[:lower:]]/ → unmatch ★ "\x{df}" =~ /(?aa)[[:lower:]]/ → unmatch "\x{df}" =~ /(?iu)[[:lower:]]/ → match "\x{df}" =~ /(?ia)[[:lower:]]/ → unmatch ★ "\x{df}" =~ /(?iaa)[[:lower:]]/ → unmatch
LATIN SMALL LETTER LONG S (U+017F, ſ) と KELVIN SIGN (U+212A, K) は、case folding によって ASCII 範囲内の文字と同一視されるという点で、特別な文字である。LATIN SMALL LETTER SHARP S (U+00DF, ß) などとは、★の部分の動作が異なる。
(?a) の動作は本当にややこしい。いっそ、(?u) と (?aa) だけにしてしまった方が分かりやすいのではないだろうか。
先日の、Ruby や鬼雲で文字クラスの動作がおかしいと思われる件について、Perl 5.14 での動作を調べてみた。
"\x{17f}" =~ /(?iu)\w/ → match "\x{17f}" =~ /(?iu)[\w]/ → match "\x{17f}" =~ /(?iu)\W/ → unmatch "\x{17f}" =~ /(?iu)[\W]/ → unmatch "\x{17f}" =~ /(?ia)\w/ → unmatch ★ "\x{17f}" =~ /(?ia)[\w]/ → match ★ "\x{17f}" =~ /(?ia)\W/ → match "\x{17f}" =~ /(?ia)[\W]/ → match "s" =~ /(?ia)\w/ → match "s" =~ /(?ia)[\w]/ → match "s" =~ /(?ia)\W/ → unmatch "s" =~ /(?ia)[\W]/ → unmatch "\x{17f}" =~ /(?ia)\p{ASCII}/ → unmatch "\x{17f}" =~ /(?ia)[\p{ASCII}]/ → unmatch "\x{17f}" =~ /(?ia)\P{ASCII}/ → match "\x{17f}" =~ /(?ia)[\P{ASCII}]/ → match "s" =~ /(?ia)\p{ASCII}/ → match "s" =~ /(?ia)[\p{ASCII}]/ → match "s" =~ /(?ia)\P{ASCII}/ → unmatch "s" =~ /(?ia)[\P{ASCII}]/ → unmatch "\x{17f}" =~ /(?iaa)\w/ → unmatch "\x{17f}" =~ /(?iaa)[\w]/ → unmatch "\x{17f}" =~ /(?iaa)\W/ → match "\x{17f}" =~ /(?iaa)[\W]/ → match
文字クラスの内外で結果が一致していないものには ★ を付けた。(?ia) が、不思議な挙動となっている。
一方、鬼雲 5.12.1 の (?ia) の挙動は以下の通り。
"\x{17f}" =~ /(?ia)\w/ → unmatch ★ "\x{17f}" =~ /(?ia)[\w]/ → match ★ "\x{17f}" =~ /(?ia)\W/ → match "\x{17f}" =~ /(?ia)[\W]/ → match "s" =~ /(?ia)\w/ → match "s" =~ /(?ia)[\w]/ → match "s" =~ /(?ia)\W/ → unmatch ★ "s" =~ /(?ia)[\W]/ → match ★ "\x{17f}" =~ /(?ia)\p{ASCII}/ → unmatch ★ "\x{17f}" =~ /(?ia)[\p{ASCII}]/ → match ★ "\x{17f}" =~ /(?ia)\P{ASCII}/ → match "\x{17f}" =~ /(?ia)[\P{ASCII}]/ → match "s" =~ /(?ia)\p{ASCII}/ → match "s" =~ /(?ia)[\p{ASCII}]/ → match "s" =~ /(?ia)\P{ASCII}/ → unmatch ★ "s" =~ /(?ia)[\P{ASCII}]/ → match ★
鬼雲の方がより怪しい動きをしているが、どういう仕様であるべきかを明確にしないことには、下手にいじらない方が良さそうである。
数日前に FreeBSD 9.0 が公開された。最後に FreeBSD をインストールしたのは 10年以上前のことだろうか。(最後に使ったのはおそらく 5年くらい前。) VMware Player に FreeBSD 9.0 (amd64) をインストールしてみることにした。
インストーラは昔とさほど変わらず、テキストベースの画面だった。キーボードの選択で jp106 と jp106x というのがあり、何かと思ったが、jp106x は、Ctrl と Caps が入れ替わっているようだ。何も考えずに、デフォルトの設定のままインストールを進めていったところ、最低限のものしか入っていない状態でインストールが完了してしまった。X やデスクトップ環境も入っておらず、ちょっと困った。FreeBSD ハンドブック を参考に、以下の手順で xorg, gnome2, VMware tools をインストールして、gnome が立ち上がることが確認できた。(最初は、VMware tools を入れない状態で X を動かしたところ、マウスが動かず途方に暮れた。)
% su # pkg_add -r xorg # pkg_add -r gnome2 # pkg_add -r compat6x-amd64 # mount -t cd9660 /dev/cd0 /mnt # tar xf /mnt/vmware-freebsd-tools.tar.gz # cd vmware-tools-distrib/ # ./vmware-install.pl # exit % echo "/usr/local/bin/gnome-session" > ~/.xinitrc % startx
FreeBSD 9.0 には、clang 3.0 が入っているので、それを使って Onigmo 5.12.1 をコンパイルできるか試してみた。
以下のコマンドで、あっさりビルドとテストが通ってしまった。あまりにも簡単で拍子抜け。
% env CC=clang ./configure % make % env LD_LIBRARY_PATH=.libs ./testpy.py UTF-8
「cp932で表現できない文字がたまに混ざるユニコード文字列をWindowsのコンソールにprintしたい場合 - 西尾泰和のはてなダイアリー」を見て、Python 2 と 3 で共通の方法で、同じことをできないかと考えてみた。
実は、以前の io.open を使った方法を使えば、io.open には errors 引数があるので、これを前述のサイトと同じように設定すれば、指定されたエンコーディングに変換できない文字を代替表記に置換して出力できる。ただ、以前の方法では、Python 自身が出力するエラー出力が Python 2 では出力されないという問題があることが分かった。
以前の方法では、Python 2 で sys.std(out|err).write 関数に bytes 型が渡されるのを回避するために、print 関数をフックしていたが、今回は sys.std(out|err).write 関数自体をフックすることにした。
class TextWriter: def __init__(self, fileno, **kwargs): kw = dict(kwargs) kw.setdefault('errors', 'backslashreplace') # \uXXXX 形式による代替表記をデフォルトに設定 kw.setdefault('closefd', False) self._writer = io.open(fileno, mode='w', **kw) self._write = self._writer.write # work around for Python 2.x self._writer.write = lambda s: self._write("" + s) # Unicode 文字列に変換 def getwriter(self): return self._writer sys.stdout = TextWriter(sys.stdout.fileno(), encoding=outenc).getwriter() sys.stderr = TextWriter(sys.stderr.fileno(), encoding=outenc).getwriter()
以上のコードで、Python 2, 3 共通の方法で、標準出力・標準エラー出力のエンコーディングと、エンコードエラー発生時の処理方法を設定できるようになった。ただ、少々長いのが難点だ。
Ruby の Redmine に、/[\W]/i の動作がおかしいとの報告が来ている。
鬼雲にも影響があるか確認したところ、/(?ia)[\w]+/ が ASCII 範囲外の U+212A (KELVIN SIGN) と U+017F (LATIN SMALL LETTER LONG S) にもマッチしてしまうことが判明。他にも、/(?iu)[\p{ASCII}]/ が U+212A と U+017F にマッチしたり、/(?iu)[\P{ASCII}]/ が "s" にマッチしてしまう。
文字クラスの中に、\w や \p{} などが含まれている際の case fold 処理に問題がある。しかし、よい修正方法が思いつかない。