Search Non ASCII with bytes

ASCII Compatible な encoding の文字列に、ASCII 文字以外が含まれているかどうか検索するという問題。Ruby においては ASCII のみの文字列は ASCII 互換 encoding の文字列と結合できるので重要な問題です。

普通に書くと以下のようになるでしょうか。しかし、これでは 1000 文字を 100 万回調べるのに 160 秒ほどかかってしまいました。

int
search_nonascii_bytes(const char* p, const char *e)
{
    for (; p < e; ++p) {
	if (!isascii(*p)) return p;
    }
    return NULL;
}

Shift_JIS 風 UTF-8 と CP932 風 UTF-8 の正規化

str.gsub!(/\xC2\xA2/, "\xEF\xBF\xA0")
str.gsub!(/\xC2\xA3/, "\xEF\xBF\xA1")
str.gsub!(/\xC2\xAC/, "\xEF\xBF\xA2")
str.gsub!(/\xC2\xA6/, "\xEF\xBF\xA4")
str.gsub!(/\xE2\x80\x94/, "\xE2\x80\x95")
str.gsub!(/\xE2\x80\x96/, "\xE2\x88\xA5")
str.gsub!(/\xE3\x80\x9C/, "\xEF\xBD\x9E")
str.gsub!(/\xE2\x88\x92/, "\xEF\xBC\x8D")

で正規化できます。

Ruby1.9 なら以下のように書いた方がベター。

str.tr("\u{301C 2016 2212 00A2 00A3 00AC 2014 00A6}",
  "\u{FF5E 2225 FF0D FFE0 FFE1 FFE2 2015 FFE4}")

Ruby 1.9 の magic comment

magic comment ですが、端的に言えば Python と同じです。異なるのは、emacs 風の -*- coding: euc-jp-unix -*- の -unix をちゃんと捨ててくれる点くらいでしょうか。これもあくまで現在の実装になります。

Ruby 1.9 の string literal encoding

string literal encoding とは文字列リテラルエンコーディングです。文字列リテラルはさらに、通常のスクリプトファイル内のものと、-e や stdin から与えられるスクリプト内のものに分けられ、両者でデフォルトの値が異なります。この辺の詳細は [ruby-dev:33368] に表がありますが、これから変わる可能性もあるのであくまで参考として。とりあえず、string literal encoding の決定の優先順位は、 magic comment > -K オプション > デフォルト となります。

Ruby 1.9 における replica と alias の違い

端的にいえば以下の通り。

replica
「encoding」を定義する
alias
「encoding *名*」を定義する

図にすると以下のような感じ。

                                鬼車(CES)
 OnigEncodingSJIS
   │←────────────────┐
───┼─────────────────┼──────────────
   │                 │   Encoding (文字集合+CES)
   │                 │
 #<Encoding:Shift_JIS> ─(replica)→ #<Encoding:Windows-31J>
   │     │           │     │
───┼─────┼───────────┼─────┼────────
   │    (alias)          │    (alias)  Encoding名
   │     ↓           │     ↓   (charset名)
  "Shift_JIS" "SJIS"       "Windows-31J" "CP932"

(鬼車層とEncoding層の区別は正確ではないのですが便宜上こうしておきます。実際には Encoding 層 での # の実体 rb_encoding* は同じもの)
 鬼車層では文字単位の操作を行っています。具体的には str[n] が何を返すか、str.length はどのくらいか。また String#valid_encoding? の true/false 判定もこのレベルです。鬼車層の実体は、文字の最小バイト数と最大バイト数の指定や、文字列の長さを算出する関数、大文字を小文字に変換する関数等をひとまとめにした構造体 OnigEncodingType で、具体的には ONIG_ENCODING_ASCII やONIG_ENCODING_SJIS 等があります。
 Encoding 層では文字列操作全般を司ります。具体的には文字列の結合可能・比較可能、正規表現のマッチ可能判定がこの層で行われています。この層での実体は rb_encoding です。Encoding 層における実体は 鬼車が native に対応している encoding から直結している original encoding、original encoding からreplicate された replica encoding、鬼車が対応していない encoding に便宜的に与えられる dummy encoding の3種類に分けることができます。これらを見分けるには Encoding#base_encoding や Encoding#dummy? を見るわけですが、見たからどうということでもありません。
 これらの区別を超越する存在が 7bit のみで構成された文字列で、String#ascii_only? で判定可能です。これが真になる文字列はいかなるencoding の文字列とも結合・比較可能となります。
 なお、rb_encoding は OnigEncodingType の同じもののため、Encoding 層でのreplica でも、鬼車レベルの諸関数、例えば文字群に Unicode Property Nameを与えたり、valid なバイト列の範囲を定義し直すことが可能です。([ruby-dev:32947] での「レプリカはコピーをした後で関数のいくつかを上書きすることも可能」とはたぶんこのこと)
 Encoding名層は、Encoding.find() するためのテーブルです。実際の構造は以下のようになっていると考えればいいでしょう。alias で追加された名前は、文字列から rb_encoding を検索する際にのみに用いられます。

ENC_TABLE = {
  'Shift_JIS' => Encoding::Shift_JIS,
  'Windows-31J' => Encoding::Windows_31J,
  'EUC-JP' => Encoding::EUC_JP
}
ENC_TABLE_ALIAS = {
  'SJIS' => Encoding::Shift_JIS,
  'CP932' => Encoding::Windows_31J,
  'EUCJP' => Encoding::EUC_JP
}
def rb_enc_alias(alias, orig)
  ENC_TABLE_ALIAS[alias] = orig
end
def Encoding.find(name)
  return ENC_TABLE[name] if ENC_TABLE[encname]
  return ENC_TABLE_ALIAS[name] if ENC_TABLE_ALIAS[encname]
  return -1
end

RubyM17N について

RubyM17N は現在「コードが仕様」な面が多いため、わたしが知っていることについて書き残しておくことにする。と言っても、わたしも追いかける身なので間違ってる点はあるだろうので、そこはご了承を。