Perl で全角半角変換をモダンに行うコードを理解する

"Perl で半角カナと全角カナの変換をする" の記事を書いたら、"404 Blog Not Found:perl – で全角半角変換をモダンに行う" という CORE Module のみを使う方法というのが返ってきたのだけれど、Perl 特有の"呪文"というか"記号のお化け"のようなコードで何をしているのかがよくわからなかった…
そこで、ちょうど短いコードでもあったので1行ずつ何をしているのか調べていった。

"全角半角変換" の仕方としては、文字名(HALFWIDTH KATAKANA VOICED SOUND MARK など)から HALFWIDTH を削除して対応する全角カナ一覧を作り、tr/// で変換している。
eval の部分は NFC で合字の処理をするのに必要なのかな?hira2kata では NFC が必要ないから eval する必要もないのだろうか。

(追記:eval の部分について)
tr/// では変数展開が行われないために、$hankaku$zenkaku を文字列内でそれぞれ変数展開してから eval するようになっている。
参考:perlop – Perl の演算子と優先順位
   tr///で変数展開するにはevalする必要があるわけですが、 – 浅倉卓司@blog風味? – ひとりでもグループ

 1 #!/usr/bin/perl
 2 use 5.008001;
 3 use strict;
 4 use warnings;
 5 use utf8;
 6 use charnames ':full';
 7 use Unicode::Normalize;
 8 
 9 {
10     my $hankaku = "\x{FF9E}\x{FF9F}";
11     my $zenkaku = "\x{3099}\x{309A}";
12 
13     for my $o (0xFF61 .. 0xFF9D){
14         $hankaku .= chr $o;
15         my $n = charnames::viacode($o);
16         $n =~ s/HALFWIDTH\s+//;
17         $zenkaku .= chr charnames::vianame($n);
18     }
19 
20     *tr_h2z = eval "sub { local $_ = shift; tr/$hankaku/$zenkaku/; $_ }";
21     *tr_z2h = eval "sub { local $_ = shift; tr/$zenkaku/$hankaku/; $_ }";
22 
23     sub han2zen { NFC(tr_h2z(shift)) }
24     sub zen2han { NFC(tr_z2h(NFD(shift))) }
25 
26     sub hira2kata {
27         local $_ = shift;
28         tr/\x{3041}-\x{3096}/\x{30A1}-\x{30F6}/;
29         $_;
30     }
31 }
32 binmode STDOUT, ":utf8";
33 local $\ = "\n";
34 print zen2han(hira2kata("「ぽげむたぴぎゃみにょーん」って最初に言ったのは?"));
35 print han2zen("ウソダドンドコドーン");
2行目(use 5.008001;)
Perl のバージョン番号を指定して、指定バージョンより新しいものであることをチェックする。
参考:perlfunc – Perl 組み込み関数
5行目(use utf8;)
Perl にスクリプトが UTF-8 で書かれている事を教える。
参考:utf8 – ソースコード内に、UTF-8(か、UTF-EBCDIC)を有効/無効にするためのプラグマ
6行目(use charnames ':full';)
ダブルクォートされた文字列内で、名前でキャラクタを呼び出す(\x{FF9E} といった部分)。
参考:Perl 5.8.x Unicode関連
7行目(use Unicode::Normalize;)

Unicode 正規化 を使って合字(ここでは濁点・半濁点)を処理する。
参考:Unicode::Normalize で遊ぶ – daily dayflower
10行目(my $hankaku = "\x{FF9E}\x{FF9F}";)
\x{FF9E} は半角の濁点(HALFWIDTH KATAKANA VOICED SOUND MARK) 、\x{FF9F} は半角の半濁点(HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK)
11行目(my $zenkaku = "\x{3099}\x{309A}";)
\x{3099} は全角の濁点(COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK)、\x{309A} は全角の半濁点(COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK)
13行目(for my $o (0xFF61 .. 0xFF9D){)
0xFF61 は半角の句点 "。"、0xFF9D は半角カタカナの "ン"
"。" から "ン" までの文字に対して処理を行う。
14行目($hankaku .= chr $o;)
chr:引数で指定したコードに対応する文字を返す。
半角の濁点・半濁点からなる $hankaku に、半角の "句点" から半角カタカナの "ン" までを追加していく。
15行目(my $n = charnames::viacode($o);)
charnames::viacode(code):コード番号 code の文字の名前を返す。(例:HALFWIDTH KATAKANA VOICED SOUND MARK
16行目($n =~ s/HALFWIDTH\s+//;)
$nHALFWIDTH  を削除する。
17行目($zenkaku .= chr charnames::vianame($n);)
charnames::viacode(name):文字の名前 name のコード番号を返す。
全角の濁点・半濁点からなる $zenkaku に、返されたコード番号(HALFWIDTH を削除したもの)を chr で文字に変換した結果を追加していく。
20行目(*tr_h2z = eval "sub { local \$_ = shift; tr/$hankaku/$zenkaku/; \$_ }";)
変数名のプレフィクスの * は型グロブ。
参考:Perl講座 2章 [変数]
$_ は入力レコード。tr/FROM/TO/ は、検索文字列 FROM に含まれる各文字を対応する置換文字列 TO にマッチする文字に1文字ずつ変換する。
参考:tr/// [Perl講座 -Smart]
23行目(sub han2zen { NFC(tr_h2z(shift)) })
NFC:2つの文字を合字にする。
参考:Macの合字ファイル名で困ったときにはUnicode::Normalizeで処理すべし – 狐の王国
shift で取り出した引数(変換対象文字列)を tr_h2z 関数に渡す。
24行目(sub zen2han { NFC(tr_z2h(NFD(shift))) })
NFD:合字を2つの文字に分解する。
shift で取り出した引数(変換対象文字列)を NFD 関数に渡して2つの文字に分解するしてから tr_z2h 関数に渡す。
27行目(local $_ = shift;)
shift 関数で @_ の先頭を切り出しサブルーチンの引数を取得する。
28行目(tr/\x{3041}-\x{3096}/\x{30A1}-\x{30F6}/;)
ひらがな から カタカナ へ変換する。
"ぁ-ゖ" を "ァ-ヶ" に変換する。

29行目($_;)
return が無い場合は、最後に評価された値が返される。
32行目(binmode STDOUT, ":utf8";)
use utf8; したので、文字列に UTF-8 フラグが付いているため PerlIO レイヤを使う。
参考:Perl 5.8.x Unicode関連
33行目(local $\ = "\n";)
出力レコードセパレータに \n を指定する。print の出力に自動的に \n が付加される。
ミニマルPerl Unix/LinuxユーザのためのPerl習得法
Tim Maher
オライリージャパン
売り上げランキング: 279701
«
»