[インデックス 11559] ファイルの概要
このコミットは、Go言語のexp/norm
パッケージにおけるUnicode正規化処理の内部的な改善を目的としています。特に、NFC (Normalization Form C) と NFD (Normalization Form D) の境界条件の統一、qcflags
(Quick Check Flags)の構造変更、およびruneInfo
のフィールド命名と型の一貫性向上に焦点を当てています。これらの変更は、将来的なテーブルフォーマットの変更に備えるための準備作業として位置づけられています。
コミット
commit 8ba20dbdb5c7a77f79409488fb4637f470ab107d
Author: Marcel van Lohuizen <mpvl@golang.org>
Date: Thu Feb 2 13:55:53 2012 +0100
exp/norm: a few minor changes in prepration for a table format change:
- Unified bounrary conditions for NFC and NFD and removed some indirections.
This enforces boundaries at the character level, which is typically what
the user expects. (NFD allows a boundary between 'a' and '`', for example,
which may give unexpected results for collation. The current implementation
is already stricter than the standard, so nothing much changes. This change
just formalizes it.
- Moved methods of qcflags to runeInfo.
- Swapped YesC and YesMaybe bits in qcFlags. This is to aid future changes.
- runeInfo return values use named fields in preperation for struct change.
- Replaced some left-over uint32s with rune.
R=r
CC=golang-dev
https://golang.org/cl/5607050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8ba20dbdb5c7a77f79409488fb4637f470ab107d
元コミット内容
このコミットは、exp/norm
パッケージにおけるUnicode正規化処理の内部的な調整を含んでいます。主な変更点は以下の通りです。
- NFCとNFDの境界条件の統一: 正規化形式C (NFC) と正規化形式D (NFD) の境界条件が統一され、間接参照が削減されました。これにより、ユーザーが通常期待する文字レベルでの境界が強制されます。NFDでは例えば「a」と「`」の間に境界を許容しますが、これは照合(collation)において予期せぬ結果をもたらす可能性があります。既存の実装は既に標準よりも厳密でしたが、この変更によりそれが正式化されました。
qcflags
メソッドのruneInfo
への移動:qcflags
型に属していたメソッドがruneInfo
型に移動されました。qcFlags
におけるYesC
とYesMaybe
ビットの入れ替え: 将来の変更を容易にするため、qcFlags
内のYesC
とYesMaybe
ビットの順序が入れ替えられました。runeInfo
の戻り値における名前付きフィールドの使用: 構造体変更の準備として、runeInfo
の戻り値で名前付きフィールドが使用されるようになりました。- 残存する
uint32
のrune
型への置換: コードベースに残っていた一部のuint32
型がrune
型に置き換えられました。
変更の背景
このコミットの背景には、Unicode正規化処理の正確性と保守性の向上が挙げられます。
- Unicode正規化の複雑性: Unicodeには、同じ文字が複数の異なるバイト列で表現される「等価性」の問題が存在します。これを解決するために、Unicode正規化形式(Normalization Forms)が定義されています。NFC (Normalization Form C) は合成済み形式、NFD (Normalization Form D) は分解済み形式です。これらの形式間での変換や、文字列の比較、検索などを行う際には、正確な境界の検出が不可欠です。
- 境界条件の統一の必要性: コミットメッセージにあるように、NFDでは「a」と「`」のような文字の間に境界を許容する場合があります。これは、結合文字(combining characters)の特性によるものです。しかし、アプリケーションによっては、このような「文字の途中」に境界があることを予期せず、照合や文字列操作で問題を引き起こす可能性があります。このコミットは、ユーザーが直感的に理解しやすい「文字レベル」での境界を強制することで、このような潜在的な問題を回避し、より堅牢な正規化処理を提供することを目指しています。
- コードの整理と将来の拡張性:
qcflags
メソッドのruneInfo
への移動や、ビットの入れ替え、名前付きフィールドの使用は、コードの論理的な構造を改善し、将来的な機能拡張やパフォーマンス最適化のための準備です。特に、テーブルフォーマットの変更が予定されており、それに先立って内部構造を整理することで、変更作業をスムーズに進める狙いがあります。 - Go言語の
rune
型への統一: Go言語ではUnicodeコードポイントを表現するためにrune
型(int32
のエイリアス)が推奨されています。uint32
が残存していた箇所をrune
に統一することで、コードの意図がより明確になり、型の一貫性が保たれます。
前提知識の解説
このコミットを理解するためには、以下の概念を把握しておく必要があります。
- Unicode: 世界中の文字をコンピュータで扱うための文字コード標準です。各文字には一意のコードポイント(数値)が割り当てられています。
- Unicode正規化 (Unicode Normalization): 同じ文字でも異なるバイト列で表現される「等価性」の問題を解決するためのプロセスです。例えば、アクセント付きの文字「é」は、単一のコードポイント (U+00E9) で表現することも、基本文字「e」 (U+0065) と結合用アキュートアクセント (U+0301) の組み合わせで表現することもできます。
- NFC (Normalization Form C): Canonical Composition(正準合成)を行った形式。可能な限り合成された文字を使用します。ウェブ上のテキストの多くはNFCです。
- NFD (Normalization Form D): Canonical Decomposition(正準分解)を行った形式。文字をその構成要素に分解します。
- 結合文字 (Combining Characters): それ自体では独立した意味を持たず、先行する文字と組み合わされて新しい文字を形成する文字です(例: アクセント記号、ダイアクリティカルマーク)。Unicodeでは、これらの結合文字が基本文字の後に続くことで、合成された文字を表現します。
rune
(Go言語): Go言語におけるrune
型は、Unicodeコードポイントを表すために使用される組み込み型です。実体はint32
のエイリアスであり、UTF-8でエンコードされた文字列からデコードされた単一のUnicode文字を保持します。runeInfo
:exp/norm
パッケージ内で、個々のUnicode文字(rune)に関する情報(サイズ、結合クラス、クイックチェックフラグなど)を保持するために使用される構造体です。qcflags
(Quick Check Flags): Unicode正規化において、特定の文字が特定の正規化形式ですでに正規化されているかどうかを高速にチェックするためのフラグの集合です。これにより、不要な正規化処理をスキップし、パフォーマンスを向上させることができます。YesC
: NFCで正規化済み。NoC
: NFCで正規化されていない(分解が必要)。MaybeC
: NFCで正規化されているかもしれないが、結合文字の有無によって変わる可能性がある。YesD
: NFDで正規化済み。NoD
: NFDで正規化されていない(分解が必要)。
ccc
(Canonical Combining Class): Unicodeの各文字に割り当てられた数値で、結合文字の表示順序を決定するために使用されます。ccc
が0の文字は非結合文字(または結合文字でない文字)です。
技術的詳細
このコミットは、exp/norm
パッケージの内部実装にいくつかの重要な変更を加えています。
-
境界条件の統一と間接参照の削減:
- 以前は
formInfo
構造体にboundaryBefore
とboundaryAfter
という関数ポインタ(boundaryFunc
型)が定義されており、正規化形式(NFC, NFDなど)に応じて異なる境界判定ロジックがディスパッチされていました。 - このコミットでは、
boundaryFunc
型とformInfo
内の対応するフィールドが削除されました。 - 代わりに、
runeInfo
型に直接boundaryBefore()
とboundaryAfter()
メソッドが追加されました。これにより、境界判定ロジックがruneInfo
にカプセル化され、正規化形式に依存しない統一された判定が可能になります。 - コミットメッセージにあるように、NFDが「a」と「`」の間に境界を許容するようなケースでも、この変更により「文字レベル」での境界が強制されるようになります。これは、
runeInfo.combinesBackward()
のチェックによって実現されます。ccc == 0
(結合文字でない)かつcombinesBackward()
がfalse
(後続の文字と結合しない)の場合にのみ境界とみなすことで、より厳密な文字境界を定義しています。
- 以前は
-
qcflags
メソッドのruneInfo
への移動:- 以前は
qcInfo
型に定義されていたisYesC()
,isYesD()
,combinesForward()
,combinesBackward()
,hasDecomposition()
などのメソッドが、runeInfo
型に移動されました。 - これにより、
runeInfo
が文字に関するすべての情報(サイズ、CCC、クイックチェックフラグ、そしてそれらに関連する判定ロジック)を一元的に管理するようになり、コードの凝集度が高まりました。
- 以前は
-
qcFlags
におけるYesC
とYesMaybe
ビットの入れ替え:qcInfo
のビット定義が変更されました。以前はNFC_QC Yes(00), No (01), or Maybe (11)
でしたが、NFC_QC Yes(00), No (10), or Maybe (11)
に変更されました。- 具体的には、
maketables.go
でQCNo
の場合にe |= 0x2
だったのがe |= 0x4
に変更されています。 - これは、
qcInfo
のビットマスクと対応するメソッドのロジックにも影響を与え、isYesC()
やcombinesBackward()
(以前はisMaybe
と同じビットを使用)の判定ロジックが調整されました。この変更は「将来の変更を容易にするため」とされており、おそらく内部的なデータ表現の最適化や、新しい正規化ルールへの対応を見越したものです。
-
runeInfo
の戻り値における名前付きフィールドの使用:runeInfo
構造体の初期化において、runeInfo{pos: bn, size: uint8(sz)}
のように名前付きフィールドが明示的に使用されるようになりました。- これにより、コードの可読性が向上し、各フィールドが何を表しているのかがより明確になります。特に、構造体の定義が変更された場合でも、名前付きフィールドを使用していればコードの変更範囲を限定しやすくなります。
-
残存する
uint32
のrune
型への置換:composition.go
、input.go
、maketables.go
、tables.go
など、複数のファイルでuint32
型がrune
型に置き換えられました。- 例えば、
reorderBuffer.decomposeHangul(r uint32)
がreorderBuffer.decomposeHangul(r rune)
に変更され、recompMap
のmap[uint32]uint32
がmap[uint32]rune
に変更されました。 - これは、Go言語の慣習に従い、Unicodeコードポイントを扱う際には
rune
型を使用するという原則を徹底するための変更です。これにより、コードの意図がより明確になり、潜在的な型変換エラーを防ぐことができます。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルと、それぞれの変更の概要は以下の通りです。
src/pkg/exp/norm/composition.go
:uint32
型をrune
型に置き換え。runeInfo
の初期化で名前付きフィールドを使用。info.flags.hasDecomposition()
のようなqcflags
メソッド呼び出しをinfo.hasDecomposition()
のようにruneInfo
の直接メソッド呼び出しに変更。
src/pkg/exp/norm/forminfo.go
:boundaryFunc
型とformInfo
内のboundaryBefore
,boundaryAfter
フィールドを削除。qcInfo
型からruneInfo
型へメソッドを移動。qcInfo
のビット定義(YesC
とYesMaybe
のビット位置)を変更し、それに伴い関連メソッドのロジックを修正。runeInfo
にboundaryBefore()
とboundaryAfter()
メソッドを追加。
src/pkg/exp/norm/input.go
:hangul
メソッドの戻り値と引数の型をuint32
からrune
に変更。
src/pkg/exp/norm/maketables.go
:qcInfo
のビット定義変更に伴うmakeEntry
関数の修正(e |= 0x2
からe |= 0x4
へ)。recompMap
の型定義をmap[uint32]uint32
からmap[uint32]rune
に変更。
src/pkg/exp/norm/normalize.go
:fd.boundaryBefore(fd, info)
のような境界判定ロジックをinfo.boundaryBefore()
のようにruneInfo
の直接メソッド呼び出しに変更。info.flags.isYesC()
のようなqcflags
メソッド呼び出しをinfo.isYesC()
のようにruneInfo
の直接メソッド呼び出しに変更。runeInfo{0, 0, 0, 0}
のような初期化をruneInfo{}
に変更(ゼロ値初期化)。
src/pkg/exp/norm/tables.go
:recompMap
の型定義をmap[uint32]uint32
からmap[uint32]rune
に変更。charInfoValues
とcharInfoSparseValues
内のqcInfo
関連の定数値を更新(0x3000
が0x5000
に、0x3300
が0x5500
に、0x3800
が0x5800
に変わるなど)。これはYesC
とYesMaybe
ビットの入れ替えによるものです。
コアとなるコードの解説
このコミットの核心は、Unicode正規化処理における「境界」の扱いと、関連するデータ構造およびメソッドの整理にあります。
1. 境界条件の統一 (forminfo.go
, normalize.go
)
以前は、正規化形式(NFC, NFDなど)ごとに異なるboundaryBefore
とboundaryAfter
関数がformInfo
構造体を介して呼び出されていました。これは、各正規化形式が文字の結合特性に基づいて異なる境界ルールを持つためです。
変更前:
// forminfo.go
type boundaryFunc func(f *formInfo, info runeInfo) bool
type formInfo struct {
// ...
boundaryBefore boundaryFunc
boundaryAfter boundaryFunc
}
// normalize.go
if p == 0 && !fd.boundaryBefore(fd, info) { ... }
変更後:
このコミットでは、boundaryFunc
が削除され、runeInfo
型に直接boundaryBefore()
とboundaryAfter()
メソッドが追加されました。
// forminfo.go
// boundaryFunc type removed
// boundaryBefore and boundaryAfter fields removed from formInfo
// New methods in runeInfo
func (i runeInfo) boundaryBefore() bool {
if i.ccc == 0 && !i.combinesBackward() {
return true
}
return false
}
func (i runeInfo) boundaryAfter() bool {
return i.isInert()
}
// normalize.go
if p == 0 && !info.boundaryBefore() { ... }
この変更のポイントは、境界判定ロジックがruneInfo
(個々の文字情報)に直接関連付けられたことです。boundaryBefore()
メソッドのロジックif i.ccc == 0 && !i.combinesBackward()
は、文字が結合文字でなく(ccc == 0
)、かつ後続の文字と結合しない(!i.combinesBackward()
)場合にのみ境界とみなすことを意味します。これにより、NFDのような形式で「a」と「`」の間に境界が許容されるようなケースでも、より厳密な「文字レベル」での境界が強制され、ユーザーが期待する動作に近づきます。
2. qcflags
メソッドのruneInfo
への移動 (forminfo.go
, composition.go
, normalize.go
)
以前はqcInfo
という別の型で定義されていたクイックチェック関連のメソッド(例: isYesC()
, hasDecomposition()
)が、runeInfo
型に移動されました。
変更前:
// forminfo.go
type qcInfo uint8
func (i qcInfo) isYesC() bool { return i&0x2 == 0 }
// ...
// composition.go
if info.flags.hasDecomposition() { ... } // info.flags is qcInfo
変更後:
// forminfo.go
// qcInfo methods moved to runeInfo
func (i runeInfo) isYesC() bool { return i.flags&0x4 == 0 } // Note: bit changed
// ...
// composition.go
if info.hasDecomposition() { ... } // info is runeInfo
この変更により、runeInfo
が文字のすべての特性(サイズ、CCC、クイックチェック情報)をカプセル化する「単一の情報源」となります。これにより、コードの凝集度が高まり、runeInfo
オブジェクトを扱うだけで文字に関するあらゆる情報にアクセスできるようになります。
3. YesC
とYesMaybe
ビットの入れ替えと定数値の変更 (forminfo.go
, maketables.go
, tables.go
)
qcInfo
の内部ビット表現が変更されました。特に、NFCのクイックチェックフラグにおけるYesC
とYesMaybe
のビット位置が入れ替えられました。
変更前 (qcInfo
のコメント):
1..2: NFC_QC Yes(00), No (01), or Maybe (11)
変更後 (qcInfo
のコメント):
1..2: NFC_QC Yes(00), No (10), or Maybe (11)
この変更に伴い、forminfo.go
のisYesC()
メソッドのロジックがi.flags&0x2 == 0
からi.flags&0x4 == 0
に変更されました。また、maketables.go
でクイックチェックフラグを生成する際の定数値(e |= 0x2
がe |= 0x4
に)や、tables.go
内のcharInfoValues
およびcharInfoSparseValues
のデータも更新されています。
これは、内部的なデータ表現の最適化であり、外部から直接qcInfo
のビットを操作することは稀であるため、主に内部実装に影響します。将来のテーブルフォーマット変更への準備として行われたものです。
4. uint32
からrune
への型変更 (composition.go
, input.go
, maketables.go
, tables.go
)
Go言語ではUnicodeコードポイントをrune
型で扱うのが慣習です。このコミットでは、コードベースに残っていたuint32
型の一部がrune
型に置き換えられました。
例 (composition.go
):
// Before
func (rb *reorderBuffer) appendRune(r uint32) { ... }
func (rb *reorderBuffer) runeAt(n int) uint32 { ... }
// After
func (rb *reorderBuffer) appendRune(r rune) { ... }
func (rb *reorderBuffer) runeAt(n int) rune { ... }
例 (tables.go
):
// Before
var recompMap = map[uint32]uint32{ ... }
// After
var recompMap = map[uint32]rune{ ... }
この変更は、コードの可読性を向上させ、Go言語の型システムとの一貫性を保つことを目的としています。rune
型を使用することで、その変数がUnicodeコードポイントを表していることが明確になります。
これらの変更は、exp/norm
パッケージがUnicode正規化をより正確に、効率的に、そしてGo言語の慣習に沿って処理するための基盤を強化するものです。
関連リンク
- https://github.com/golang/go/commit/8ba20dbdb5c7a77f79409488fb4637f470ab107d
- https://golang.org/cl/5607050 (Gerrit Code Review for this commit)
参考にした情報源リンク
- go.dev - Unicode Normalization in Go
- go.dev -
golang.org/x/text/unicode/norm
package documentation - Unicode Standard Annex #15: Unicode Normalization Forms
- Wikipedia - Unicode Normalization
- Microsoft Learn - Unicode Normalization Forms
- Mozilla Developer Network - Unicode Normalization
- Medium - Unicode Normalization Forms Explained
- Go Language Specification - Runes