Ruby で新しい Unicode 規格にバージョンアップする時になにを行っているのかまとめてみた

Ruby は定期的に Unicode の新しい規格に対応しているんですがその時になにが行われているのかが気になったので調べてみました。
この記事では『Ruby の Unicode 14.0.0 対応がなにを行っているか』を追っていきたいと思います。
ちなみに Unicode 14.0.0 は次の Ruby 3.2 でサポートされる予定です。

RubyUnicode 14.0.0 対応

RubyUnicode 14.0.0 対応のチケットは以下になります。

上のチケットの作業コミットはコメントに書かれているんですが以下の通りです。

commit 48f1e8c5d85043e6adb8e93c94532daa201d42e9
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Tue Mar 15 18:55:16 2022 +0900

    Fix version check to use Emoji version for emoji-variation-sequences.txt

commit 56d9d78f14b73cb9f609558e6b760dde50872fb6
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Tue Mar 15 17:17:15 2022 +0900

    Remove Unicode 13.0.0 related files

commit 267f0089d3255c1f06ab5adf9f6c77b1ccfd2771
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Mon Mar 14 08:37:53 2022 +0900

    clarify meaning of version guards for Unicode version specs [ci skip]

commit 1b571d0abf6070673320b11a30769bbe74d12e39
Author: Benoit Daloze <eregontp@gmail.com>
Date:   Sun Mar 13 13:18:56 2022 +0100

    Fix guards for unicode versions specs

commit 45187a0fcddecc74dacc1881f2405a5ebe198081
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Sun Mar 13 10:52:24 2022 +0900

    comment out failing Unicode/Emoji version checks temporarily

commit 8f59482f5d1f77542fe3c8e8434cb1dbbc764ecf
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Sat Mar 12 21:33:51 2022 +0900

    add some tests for Unicode Version 14.0.0

commit 9b545b0caf2ccc89718ba02ff631d2a68b96a831
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Fri Mar 11 17:18:42 2022 +0900

    update specs to check for Unicode Version 14.0.0/Emoji Version 14.0

commit 2672502457523317268ac24704cf85df91e2cae6
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Fri Mar 11 17:11:32 2022 +0900

    mention Unicode Version 14.0.0

commit 8e1f3a96aecb3defc34556d75e3d2a0867416082
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Fri Mar 11 16:48:26 2022 +0900

    switch UNICODE_BETA back to NO

commit 45e0711f29f9ce65cd34ad14e3af1546ccc7252e
Author: Martin Dürst <duerst@it.aoyama.ac.jp>
Date:   Thu Dec 9 16:41:09 2021 +0900

    update Unicode Version to 14.0.0 and Emoji version to 14.0

と、いうことでまずはこのコミットの内容を下から時系列に1つ1つ見ていこうかな、と思います。

[45e0711f29] update Unicode Version to 14.0.0 and Emoji version to 14.0

common.mkUNICODE_VERSIONUNICODE_EMOJI_VERSION のバージョンが 14 に更新されています。
UNICODE_EMOJI_VERSION が別にあるんですね?
また作業当時はまだ beta 版だったので UNICODE_BETA = YES となっているぽい?
他には enc/unicode/14.0.0/casefold.henc/unicode/14.0.0/name2ctype.h の2つのファイルが新しく追加されて lib/unicode_normalize/tables.rb が更新されています。
このあたりのファイルはあとで詳しくみていきましょうかね。

[8e1f3a96ae] switch UNICODE_BETA back to NO

common.mkUNICODE_BETANO になっています。

[2672502457] mention Unicode Version 14.0.0

NEWS.md にドキュメントを追加しています。

[9b545b0caf] update specs to check for Unicode Version 14.0.0/Emoji Version 14.0

RbConfig::CONFIG['UNICODE_VERSION']RbConfig::CONFIG['UNICODE_EMOJI_VERSION'] に対するテストが追加されています。
これでバージョンを取得できるんですね、へー。

p RbConfig::CONFIG['UNICODE_VERSION']
# Ruby 3.1 => # "13.0.0"
# Ruby 3.2 => # "14.0.0"

p RbConfig::CONFIG['UNICODE_EMOJI_VERSION']
# Ruby 3.1 => # "13.1"
# Ruby 3.2 => # "14.0"

UNICODE_VERSIONUNICODE_EMOJI_VERSION で同じバージョンとは限らないんですね、へー。

[9b545b0caf] add some tests for Unicode Version 14.0.0

正規表現\p (Unicode プロパティによる文字クラス指定) のと String#upcase のテストが追加されています。

[45187a0fcd] comment out failing Unicode/Emoji version checks temporarily

テストをコメントアウトしています。何かあったんですかね?

[1b571d0abf] Fix guards for unicode versions specs

テストのコメントアウトを元に戻しています。

[267f0089d3] clarify meaning of version guards for Unicode version specs [ci skip]

テストのインデントを調整したりコメントを追加したりしています。

[56d9d78f14] Remove Unicode 13.0.0 related files

enc/unicode/13.0.0/casefold.henc/unicode/13.0.0/name2ctype.h を削除しています。
この2つはバージョンごとに新しいファイルを追加している感じですかね?

[48f1e8c5d8] Fix version check to use Emoji version for emoji-variation-sequences.txt

テストで参照しているバージョンを UNICODE_VERSION から EMOJI_VERSION に変更しています。
ただ、これは別のコミットで元に戻されているぽいですね。


と、いう感じでコミットを1つ1つみてみました。
実際の Unicode 対応としては、

  • enc/unicode/14.0.0/casefold.h
  • enc/unicode/14.0.0/name2ctype.h
  • lib/unicode_normalize/tables.rb

の3つのファイルが重要そうですね。
今度はこの3つのファイルがなにをやっているのか見ていきたいと思います。

enc/unicode/14.0.0/casefold.h

enc/unicode/14.0.0/casefold.h には Unicode の大文字、小文字の対応表が定義されています。
例えば Unicode 14.0.0 で追加された U+2C2F という文字も enc/unicode/14.0.0/casefold.h に新しく追加されています。

# Ruby 3.2 だとちゃんと小文字に変換される
p "\u2C2F".downcase.codepoints.map { _1.to_s(16) }
# Ruby 3.1 => ["2c2f"]
# Ruby 3.2 => ["2c5f"]

この enc/unicode/14.0.0/casefold.h ファイルは CaseFolding.txt を元に指定 enc/unicode/case-folding.rb機械的に生成されているみたいです。

enc/unicode/14.0.0/name2ctype.h

enc/unicode/14.0.0/name2ctype.h には正規表現POSIX 文字クラスの [:alpha:][:lower:]Unicode プロパティなどのメタデータが定義されています。
例えば Unicode 14.0.0 で追加された U+0870 という文字も enc/unicode/14.0.0/name2ctype.h に新しく追加されています。

p "\u0870" =~ /[[:alpha:]]/
# Ruby 3.1 => nil
# Ruby 3.2 => 0

この enc/unicode/14.0.0/name2ctype.h ファイルは tool/enc-unicode.rb から機械的に生成されているぽい?

lib/unicode_normalize/tables.rb

lib/unicode_normalize/tables.rbenc/unicode/14.0.0/casefold.henc/unicode/14.0.0/name2ctype.h とは違って新しくファイルが追加されているわけではなくて既存のファイルを更新しています。
lib/unicode_normalize/tables.rb には Unicode 正規化を行うためのメタデータが定義されています。
例えば NFKC で正規化した際の ① => 1 という変換テーブルもここで定義されています。

p "".unicode_normalize(:nfkc)
# => "1"

Unicode 14.0.0 で追加された U+A7F2 という文字も lib/unicode_normalize/tables.rb に追加されており、この文字を正規化すると "C" になります。

p "\uA7F2".unicode_normalize(:nfkc)
# Ryvt 3.1 => "\uA7F2"
# Ruby 3.2 => "C"

lib/unicode_normalize/tables.rb ファイルもまた template/unicode_norm_gen.tmpl を元にして機械的に生成されているぽいですかね?

まとめ

と、いう感じで簡単にですが RubyUnicode 14.0.0 に対応した時になにをしているのかを簡単にまとめてみました。
Ruby 側で対応している項目自体は多い(Unicode のアップデート内容による)んですが全体的に自動生成されるような仕組みがあるようなのですごい。
あと大文字小文字の変換テーブルや正規表現POSIX 文字クラス、Unicode 正規化のメタ情報も Ruby の処理系で保持しているんですね。
具体的にこのあたりのデータがどう使用されているのかあんまりわかっていないので次はこのあたりの実装がどうなっているのか調べてみるのとこあおもしろそうですねー。
次は Unicode 15.0.0 の対応もはじまると思うのでその時にまたどういうような対応を行っているのか追えるとよいですねー。