機能検討 - 国際化

まともな国際化を。minilang は Unicodeベースです。

構文

識別子

ASCII文字だけを特別扱いするのではなく, 漢字・ひらがなを含め, 多様な文字を使えるようにします。

Rubyに合わせて, 大文字で始まるのは定数, 小文字はローカル変数です。メソッド名は小文字始まりのみ [[incompat]]。とりあえず, 次のようにしてみました。\p{ } は, Unicodeプロパティ名 (Unicode property name) です。

CapitalLetter = \p{Lu} | \p{Lt}

NonCapitalLetter = \p{Ll} | \p{Lm} | \p{Lo} | "_" | [\x3400-\x4db5] | 
                   [\x4e00-\x9fcc] 

IdentifierPart = ( CapitalLetter | NonCapitalLetter | \p{Mn} | \p{Mc} | 
                   \p{Nd} | \p{Nl} )*

ConstIdentifier = CapitalLetter IdentifierPart

Identifier = NonCapitalLetter IdentifierPart ( "!" | "?" )?

ホワイトスペース

次の文字は空白として扱います。当然, 全角空白もホワイトスペースです。

WhiteSpace = [\t\x{FEFF}\x{000b}\x{000c}\p{Zs}]

テキスト (文字列)

要求

まずどのぐらいのレベルを目指すか, です。

よくある状況として, 一枚のWebページに、いろいろな国の人が書き込んだ内容を表示することを考えてみてください。1枚の、という時点で、一つの文字列にいろいろな国の文字を含められるようにしなければならない, と分かります。

現実的にこれを実現する方法は, 一つの文字列に複数の文字集合 character sets を含めるか, すべての文字を含む文字集合を使うか, のいずれかです。

Emacs MULE (MULtilingual Enhancement)

Emacs は, 古くから, 一つのテキストの中で実用的に多言語を扱えました。

この機能は, 当初は Emacs に対する拡張として提供されていて, 1995年には Emacs 19.28ベースの Mule 2.3 (末摘花) がリリースされていました。末摘花, ということは, その前に5つバージョンがあります。(整理されたページがありました; History of Emacs and Mule)

Mule は, それぞれの文字を, {文字集合を表すコード + その文字集合内のコード} という組で表現したようです。これにより, 一つのテキスト (文字列) 内で, 複数の文字集合の文字を同居させ, 多言語を実現していました。

  • http://www.m17n.org/mule/JAPANESE/Mule.ja.html [リンク切れ]

その後, 1999年頃 (?) には UTF-8 などを扱う Mule-UCS が出ていたようです。

そこからだいぶ経って, Emacs23で, 上の (文字集合 + その中でのコード) というやり方を捨て, 内部コードが Unicode になりました。これが2009年7月.

Ruby 1.9

ruby 1.9.1は2009年1月にリリースされました。くしくも Emacsがバージョン23でUnicodeベースになった年です。

ruby 1.9では, 文字列がその文字コード (エンコーディング) を保持するようになりました。Rubyist Magazine - Ruby M17N の設計と実装 文字集合ではなく誤って文字コードを使ってしまっています。

とても残念ですが, 文字列が文字コードを持つ, ただそれだけ。多用字系ですらない。こんなものを多言語 (multilingual) と呼ぶのは, どうかしているとしか思えません。

Encoding::Converter クラスが文字コードの変換を担当します。

CSI (Character Set Independence)

複数の文字集合による多言語化は, ボランティアベースで実用的な水準に至ることは、ほぼ不可能です。CSI は理想に見えますが, それはただの青い鳥です。

ざっと考えただけでも,

  • ある文字集合から別の文字集合への変換はどうするのか. CSIを徹底するには多対多になるが, 組み合わせ爆発する
  • 複数の文字集合が混在したとき, 文字の同定はどうするのか。
  • Collation (照合順序) はどうするのか. 文字集合が違う場合は何らかのマッピングを行うのか
  • どこでline breakするのか
  • 正規化はどうするのか

などなどを, *すべての*文字集合に対して考えなければなりません。不可能ではないでしょうが, それは商用プロダクトでしかできない / 商用プロダクトでも難しい領域です。

加えて、絵文字など Unicode にしか収録されていない文字が大量に増えてきました。

現在でも更新が続いているライブラリには, 例えば The m17n project があります。

minilangの実装

大きな文字集合にするか, 小さな文字集合を切り替えるか, ですが, 効率性の面から, 大きな文字集合のほうがいいでしょう。

2012〜3年現在、上に書いた要求を実用的にこなせるのは、Unicodeだけです。Character Set Independence (CSI) でもいいのですが、でも、いつもUnicodeを選択している状態になります。

ということで、minilang は文字列をUnicodeベースにしてみました。minilang における「文字」は、UTF-16 code unitで、文字列はその並びです。UTF-16はサロゲートペアがあるので, 1または2 code unitで Unicode code point になります。

サロゲートペアは、開発者 (minilangの利用者) が適切に対応してください。

本物の文字とUnicodeコードポイントは, 一致することもあれば, 一致しないこともあります。複数のコードポイントが合成されることが多々あります。Forms of Unicode

ruby 1.9 で String#[] が文字列を返すように変更されましたが, これは妥当な変更です。一つの code unit はサロゲートペアでは意味がなく、さらに一つの code point も文字の構成部品にしかならない場合も多いです。

IO

IOへのインパクトのほうが大きいです。

Ruby 1.9の String はただのバイト列なので、テキストファイルであれバイナリファイルであれ、IO の各メソッドが同じ型を返せます。

minilangでは、String をUnicode文字列と決めたので、バイナリファイルの入出力では使えません。

どのようにクラスを実装すべきでしょうか?

ファイル (IO) の用途を考えると、次に分けれるでしょう。

  1. XMLファイルや対話的にファイルを開く場合など、あらかじめ文字コードが決まっているか外から与えられるテキストファイル
  2. テキストファイルだが、ファイルの文字コードが不明で、自動判定したい
  3. バイナリファイル

1番目のケースは、単純に、読み書きのときに変換すればよろし。読み込み、書き込みの型は String

2番目、3番目のケースでは、String は使えません。

minilang では, 型 (インタフェイス) が違うので、バイナリファイルを扱う IO と、テキストファイルを扱う TextReaderWriter の二つのクラスに分割しました。

互換性をある程度確保するために, File.open() がその引数に応じて IOTextReaderWriter のいずれかのオブジェクトを返します。

IO の実装が軽くなる, という利点もあります。

ファイルを開くときに、文字コードを与えなかったとき

昔ならロケールにもとづく文字コードと見なすのがよかったでしょうが、現在では、文字コードが決め打ちのファイルも考えられるので、適切ではありません。

外部エンコードが指定されていないときは、バイナリファイルと見なすようにします。