文字コード事情

UTF-16に変換したい

wchar_tなAPIを呼びたんだけど、文字列をUTF-16に変換するにはどうしたらいいかな。
ほらAda.Characters.Conversionsの変換関数って、Stringの各バイトをそのままWide_Stringに格納してるだけじゃん。手抜き実装だよね。

手抜きではないですよ。
それが規格通りの正しい実装です。

えー、マジかい。

マジです。……困ったことにね。
規格のこの部分を読んでみてください。

……。

おーい?

……。

はいはい、そんな目をしなくても読んであげますよ。私だって英語は苦手なんですからね……。
文中のISO/IEC 10646:2003というのはUnicodeのことです。
で、CharacterはUnicodeの頭から256コードポイント、Wide_Characterは頭から65536コードポイント、Wide_Wide_Characterは全てのコードポイントが入ります。
つまりCharacterはLatin-1、Wide_CharacterはUCS-2、Wide_Wide_CharacterはUCS-4固定です。
UTF−8やUTF-16も含めたマルチバイト文字の存在する余地は一切ありません。
あ、文字ではなくてコードポイントと言ってるのは、Unicodeには合成文字やなんかがあるからですよ。

つまり、Ada.Characters.ConversionsはLatin-1をUCS-2に変換するから、各バイトをばらすだけでいい、と?

その通り。

互換性とかよくわかってないから言うけど、すごく酷い規格のような気がする。

その通り。

結局、僕はどうすればいいのさ。

Ada 2012では、Ada.Strings.UTF_Encodingという、UTF-8とUTF-16の間で変換を行うライブラリが追加されますね。
gcc-4.6以降に入るみたいですから、それを使えば目的は達成できると思いますよ。

僕の環境はWindowsだよ?StringにはシフトJISが入ってるに決まってるじゃないか。
まさかこれが規格違反だなんてショックだけど、実際問題他にどうしようもないよ。

文字列リテラルであれば、ソースコードをUTF-8にしてpragma Wide_Character_Encoding (UTF8);で直接UTF−16なWide_String型の定数を書けますけれども……。

リテラルもそうだけど、全部が全部そうじゃないよ。
一般的な変換関数はないの?

WindowsならMultiByteToWideChar呼びましょう。
クロスプラットフォームにしたいならiconv呼びましょう。
それが一番手っ取り早いです。

ファイル名の問題

ちょっと待ってよ。
ファイル名を扱う関数って全部Stringだよね。
もしかして規格を遵守すると、日本語ファイル名を扱う手段がない?

その通り。

おかしいと思ってたんだよね。
何もしてないのにAda.Directories.SearchがName_Errorを出すんだもの。それも特定のディレクトリでだけ。
それでわかった。ダメ文字問題だよね。原因はバックスラッシュかあ。

MBCSのことなんかまるで考えられてないファイル名チェックが入ってますからね。
a-dirval.adbを見ると、バックスラッシュ以外にもかなり酷いパスの「チェック」がなされてるのを確認できます。Name_Errorなんて実際にOSのAPIを呼んだあとでエラーコード見て出せばいいはずなんですが。

素通しなアプリが何もせずにUNCパスに対応できたのに、親切なアプリほど対応が遅れたのを思い出すね。

……UNCパスってなんでしたっけ?

君もドザだったろう。すっかりジョブズに洗脳されたね。
\\computer\shared\path...みたいなパスのことだよ。

……ああ、そんなのありましたね。

しかし21世紀にもなってこれは酷いなあ。
Ada.DirectoriesってAda2005で新しくできたパッケージだろう。

GNATの対応

AdaCoreも問題の認識だけはしてるらしくて、例えば、Text_IOやなんかでは、Formパラメータにエンコーディングの指定がいろいろできるのですが。

頑張ってるように見えるね。

実際には、頼むから素通ししてくれ、ってぐらいに使えません。
そもそも関数によって、対応状況にバラつきがありますしね……。
ある関数では変換が行われ、ある関数では素通しです。

Ada.Directoriesも、素通しな上で、Latin-1を前提としたチェックが入ってしまってるわけか。

ですです。
どうせ弾くなら、ちゃんとLatin-1に変換しやがれと。いや、実際に変換されたらもっと困りますけどね。

Latin-1の範囲で用が済んでしまう人達がマルチバイト対応したときの典型だなあ。
テスト環境も無いだろうしなあ……。

Unicode識別子は.aliファイルに正しく記録されないなど、まあ色々あります。
マルチバイトではない文字コードでもおかしいですよ。
GNATの拡張で、Ada.Characters.Latin_9というLatin-9の文字定数を定義したファイルがあるのですが。

確かLatin-9ってヨーロッパの言語の文字コードだよね。
AdaCoreはフランスにも拠点があるらしいし、いいんじゃない?

確かにAda.Characters.Latin_9だけなら、Stringに無理やりLatin-9を入れて使うためのもの、と考えることもできます。
しかし、Ada.Characters.Wide_Latin_9も存在しているのを見ると、頭が痛くなります。
私は先頭256文字がLatin-9そのままの、一文字16ビットなコード体系なんて知らないのですが、存在するのでしょうか?

ええと、ファイル頭のこのコメントはなんて書いてるんだい?

おーい。

いいから読んでよ。話が進まないよ。

まったく……この拡張はRM (A.3.3(27))で許されてるって書いてますね。

いいんだ?

でも、a-cwila9.adsではこうなってますが……。

   Euro_Sign : constant Wide_Character := Wide_Character'Val (164);

こうあるべきじゃないかなー、と思うんですよ。

   Euro_Sign : constant Wide_Character := Wide_Character'Val (16#20AC#);

20ACはUnicodeでのユーロ記号の位置かい。

そうです。そもそも規格の、追加のキャラクターセット用の定義を許す文面はこういうことであって、CharacterがLatin-1、Wide_CharacterがUCS-2、Wide_Wide_CharacterがUCS-4ということ自体を置き換えてしまっていいということにはなってない気がするんですよね。そんなことが許されてるなら、こんな末端のライブラリではなくて上で見たCharacterの定義の方に書かれてると思うのですが。
……規格でもEBCDICだけは許されてますね。なんだかなあ。

解釈の問題は難しいね。

ちなみにAARMのほうにも何も書かれてないですね……。

そういえば前に、-gnatWオプションにJIS系の文字コード(シフトJISかEUC-JP)を指定した場合、Wide_StringにJISコードが入るのに文句言ってたね。

あれはなんか、BSD系のwchar_tだとそうっぽいですね。Unicode固定ではない。
それを意識してるのかはわかりませんが、っていうか偏見でもってしてないと思ってますが。

Cランタイムの場合

wchar_tの話はInterfaces.Cでも出たね。

基本的にAdaを作ってる連中……規格も実装も両方……はwchar_tについて、といいますかそもそも文字コードついて根本的に勘違いしてるとしか思えないんですよね。
そもそもCランタイムが既にgdgdなのに、無理解なままCランタイムの上にAdaランタイムを構築するから二重の意味でgdgdなんですが。

僕なんかはWindows専門だから、charはシフトJIS、wchar_tはUTF-16だとまず思い込みがあるなあ。
ま、Windowsに限っても、多言語対応するならcharのほうは決め打てないとはわかってはいるけどね。

マルチプラットフォームまで視野に入れるなら、wchar_tのほうも決め打てなくなりますね。

それでchar16_t、char32_tができたんだろう。
今後はこっちを使えばUnicode決め打ちでソースが書けるはずと思っているんだけど。

そのはずなんですけどね、このへんを読む限り、あまりにも貧弱といいますか、MBCSとの間での変換関数しか用意されないっぽいですよ?
char16_tとchar32_t間の直接変換も見当たらないので、標準ライブラリだけでUTF-16/32間の変換をやろうとすると、一旦ロケール依存のMBCSを介して、ロケールに無い文字が抜け落ちたりしそうで。

WindowsだとコードページをUTF-8にできないからなあ。
あー、まあ、出始めってことでしょうがないんじゃない?
文字コードを明示した文字列リテラルが書けるのは大きいと思うよ。

確かに、charもwchar_tもロケール依存実装依存ですと、今までASCII以外の文字列リテラルを複数環境対応で書く方法が全くありませんでしたからね。

GNATの対応、これから

Adaの場合、Wide_Wide_StringがUCS-4固定なら、その点はCより優れてるよね。

それを活かせるライブラリがもう全く存在しませんけどね……。

ファイル名やらなんやらの引数が全部Stringなのはわかったけど、でも、ほら、一応Wide_Wide_Text_IOとかあるじゃない?
これで出力したらUnicodeなWide_Wide_StringがシフトJISに変換されて……

じー……。

……来ないんだね。
そんな気はしてたよ。

あくまでGNATの実装のせいではありますけどね、iconvなんかの外部ライブラリを使わない前提だとUnicodeとロケールの文字コードを変換する方法が無いってCランタイムの事情もあります。char32_tが実装されるのを待たないといけません。
でもまあWindowsであればWideCharToMultiByteでいいわけですし、実際に一部ではStringをUTF-8とみなしてWideCharToMultiByteで変換してたりしますので、Wide_Wide_Text_IOは手抜き確定と思ってますが。

待ってくれよ。
そうすると、StringにUTF-8を入れるとWindowsでちゃんと動く箇所があるってのかい?

Text_IOやなんかのOpen関数なら、普通に使っているとMultiByteToWideChar(CP_ACP..でコードページからUTF-16に、Formパラメータに"encoding=utf8"って文字列を渡すと、MultiByteToWideChar(CP_UTF8...でUTF-8からUTF-16になって、どちらもwfopenに行き着きます。

お、徐々に対応は進んでるじゃあないか。
君がボロクソに言うものだから心配したよ。
この分だとAda.Directoriesもそのうち直るかも。

Ada.Directoriesは……おや、しばらく見てなかったら、gcc-4.6……いや、4.5系かな……で手が入ってる気配が。

おお、素晴らしいじゃないか。
やっぱり根拠のない誹謗中傷は良くないよね。

Validityは相変わらずっぽいので実用にはまだ遠そうですが……というのは置いておいて。
そもそもStringに入るべきはロケールでもUTF-8でもなくてLatin-1なんですからね……。
他のファイル名を取る関数がLatin-1前提だったり素通しだったりするのに局所的に対応されてもなあ、というのがまずひとつ。
これだって今君に言われなきゃ見返したりしませんでしたよ。

……全部素通しのほうがマシ状態の一翼ってわけか。

ですです。
今の中途半端な対応状況では、どれがLatin-1動作でどれがコードページ動作でどうすればUTF-8動作に変わるだなんて、正直ソースを追っかけていかないとわかりませんから……。

まずひとつ、ってことは他にも?

使用するコードページの設定をどこから取ってくると思います?

なんだい藪から棒に。GetACPでいいじゃないか。もっと手の込んだAPIでもいいだろうけどさ。

環境変数GNAT_CODE_PAGEを参照します。

……。

しかも有効なのはGNAT_CODE_PAGE=CP_ACPとGNAT_CODE_PAGE=CP_UTF8の2種類です。

……わけがわからないよ。