Ada 2020 (34日目) - ソースコードのUnicode表現

文字コード回。

これまでのあらすじ

処理系と文字コードの関係にはふたつの側面があります。 ライブラリが文字列を処理するときの振る舞いと、処理系がソースコードを解釈するときの振る舞いとです。

標準ライブラリの実装が使用しているUnicodeのバージョンは(28日目に見ましたように) Ada.Wide_Characters.Character_Set_Version で調べられます。

今回は処理系の側の話です。

Unicodeのバージョンの更新

規格が参照しているUnicodeのバージョンがISO/IEC 10646:2017に更新されました。

Unicodeのバージョン番号で言うと10.0相当になります。 IPAが文字情報基盤整備事業で整備していた漢字が全て収録されたバージョンでもあります。

ソースコードのUnicode表現

しかしまあ、普通に考えればUnicodeのバージョンの違いなんてUTFの定義が変わったみたいなレベルでなければ処理系には関係なさそうなものです。 普通はコメントと文字列リテラル以外はASCIIやLatin-1の範囲で書きますよね。

しかしAda 2005以降はUnicode識別子が認められました。 それどころか空白や改行等も全てUnicodeを通じて定義されるようになりました。

実は、空白文字としていつも使っているSPACE(U+0020、半角スペース) 以外にもUnicodeカテゴリZsに含まれるNO-BREAK SPACE(U+00A0) やIDEOGRAPHIC SPACE(U+3000、全角スペース)が使えたりするのです。 Zsに含まれる文字が増えたら空白文字として使える文字も増えることになってしまいます。

Unicode識別子だけを取り上げましてもAdaの識別子はcase insensitiveです。 一致しているか比較のためにはcase foldingを行わなければなりません。

というわけで頭が痛くなるような話ではありますがUnicodeのバージョンが処理系の振る舞いに影響を与えてしまうわけです。

ソースコードの正規化形式

Unicode識別子に使える文字には次の条件があります。

  • 先頭はカテゴリLu、Ll、Lt、Lm、Lo、Nlの文字が使用できます。

  • 2文字目以降は加えてカテゴリMn、Mc、Nd、Pcの文字が使用できます。

  • NFKC正規化を行って変化してしまう文字の扱いは実装依存です。

つまり実装はUnicode識別子にNFKC正規化を行っても行わなくても構いません。 実装依存ですのでコンパイルエラーにするのもありです。

A (半角) と (全角)は同じ識別子かもしれませんし違うかもしれません。

ミリ は同じ識別子かもしれませんし違うかもしれません。 正規化されない場合は はSoですので確実にコンパイルエラーです。

実装依存とはいえ流石に (全角)と (全角)が同一視されるようなNFKCとかけ離れたクソ変換、例えば全て同じ文字に置き換える等、をする処理系はないでしょう……と思いたいです。

更にソースコード上に出現できる全ての文字には次の条件があります。

  • NFC正規化を行って変化してしまう文字の扱いは実装依存です。

つまり文字列リテラルの中であっても互換文字やNFD形式で書いた文字がそのままコンパイル後の文字列定数になる保証はありません。 勝手に正規化されているかもしれません。

文字列リテラル "神" (U+FA19)は "神" (U+795E)に変化するかもしれませんししないかもしれません。 実装依存を踏まないようにするには Wide_Wide_Character'Val (16#FA19#) 等とする必要があります。

というわけでポータブルなソースコードを書こうと思えば、識別子はNFKCで変化しない文字のみ、文字列リテラルはNFCで変化しない文字のみで書く必要があります。

Ada 2005では文字列リテラルの中であってもNFKC正規化が行われるかもしれませんでした。 Ada 2020でNFKCは識別子のみ、それ以外はNFCとなりました。

GNATでは

ちなみにGNATではUnicodeをソースコード上に使えはします。 バージョンは未だにISO/IEC 10646:2003、Unicode 4.0です。 正規化は行いません。

標準ライブラリへの追加

Ada.Wide_Characters.Handling.Is_NFKC

何故か同じAIの中で Ada.Wide_Characters.Handling.Is_NFKC が追加されています。 NFKC正規化を行って変化しない文字に True を返します。

列挙型の要素にUnicode識別子を用いた場合 'Wide_Val/'Wide_Wide_Val 属性でも処理できる必要がありますので、どうせNFKCを実装する必要があるのならということでしょうか。 判定のみで変換関数がないのは、NFKC正規化を行って変化してしまう文字はエラーにする処理系では変換までは必要ないからでしょう。

関連AI

  • AI12-0004-1 Normalization and allowed characters for identifiers

  • AI12-0263-1 Update references to ISO/IEC 10646

所感

ネガティブな感想しかありません。

Python3の場合

Python3でも識別子はNFKC正規化されるそうです。