haxe.Utf8の非互換性
次のコードの出力を各プラットフォームで比較する。(現時点のGitHub上にある開発版を使う。)
import haxe.Utf8; class Hello { public static function main() { trace("あ𠀀い"); trace("あ𠀀い".length); trace("あ𠀀い".charCodeAt(1)); trace("あ𠀀い".charCodeAt(2)); trace(Utf8.length("あ𠀀い")); trace(Utf8.charCodeAt("あ𠀀い", 1)); trace(Utf8.charCodeAt("あ𠀀い", 2)); } }
文字列"あ𠀀い"はコードポイントで表すと[U+3042 U+20000 U+3044]というコードポイント数3の文字列だ。コードポイント数は文字数とは必ずしも一致しないが、コードポイント数を文字数とみなして処理をすることも多い。例えば、Twitterの140文字は、実際には140コードポイントだ。
文字列は各処理系内ではエンコーディングされた形のまま処理される。Haxeの対応しているプラットフォームでは、PHP・Neko・C++は内部エンコーディングにUTF-8を使っている。一方、JavaScript・C#・Java・SWFは内部エンコーディングにUTF-16を使っている。
String#lengthは文字列のユニット数を返す。UTF-8では[e3 81 82 f0 a0 80 80 e3 81 84]、UTF-16では[3042 d840 dc00 3044]のようにエンコーディングされている。そのため "あ𠀀い".length はUTF-8の環境では10になるが、UTF-16の環境では4になる。
これでは不便なのでhaxe.Utf8が提供されているのだろうが、実際には非互換の部分がある。Utf8.lengthはUTF-8環境ではコードポイント数が返る。しかし、UTF-16環境ではそのままUTF-16のユニット数が返ってしまう。UTF-16では、BMP(U+0000〜U+FFFF)の文字だけであればユニット数とコードポイント数は一致するが、先の例のようにbeyond-BMP(U+10000〜U+10FFFF)を含む場合、この文字はサロゲートペアを使って2ユニットで表現されるので、ユニット数とコードポイント数は一致しなくなる。
haxe$ php php/index.php Hello.hx:7: あ𠀀い Hello.hx:8: 10 Hello.hx:9: 129 Hello.hx:10: 130 Hello.hx:11: 3 Hello.hx:12: 131072 Hello.hx:13: 12356 haxe$ neko hello.n Hello.hx:7: あ𠀀い Hello.hx:8: 10 Hello.hx:9: 129 Hello.hx:10: 130 Hello.hx:11: 3 Hello.hx:12: 131072 Hello.hx:13: 12356 haxe$ cpp/Hello Hello.hx:7: あ𠀀い Hello.hx:8: 10 Hello.hx:9: 129 Hello.hx:10: 130 Hello.hx:11: 3 Hello.hx:12: 131072 Hello.hx:13: 12356 haxe$ node hello.js あ𠀀い 4 55360 56320 4 55360 56320 haxe$ mono cs/bin/cs.exe Hello.hx:7: あ𠀀い Hello.hx:8: 4 Hello.hx:9: 55360 Hello.hx:10: 56320 Hello.hx:11: 4 Hello.hx:12: 55360 Hello.hx:13: 56320 haxe$ java -jar java/java.jar Hello.hx:7: あ𠀀い Hello.hx:8: 4 Hello.hx:9: 131072 Hello.hx:10: 56320 Hello.hx:11: 4 Hello.hx:12: 131072 Hello.hx:13: 56320
SWFは「𠀀」の部分が豆腐に化ける以外はC#と同じ結果になる。