Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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におけるYesCYesMaybeビットの入れ替え: 将来の変更を容易にするため、qcFlags内のYesCYesMaybeビットの順序が入れ替えられました。
  • runeInfoの戻り値における名前付きフィールドの使用: 構造体変更の準備として、runeInfoの戻り値で名前付きフィールドが使用されるようになりました。
  • 残存するuint32rune型への置換: コードベースに残っていた一部のuint32型がrune型に置き換えられました。

変更の背景

このコミットの背景には、Unicode正規化処理の正確性と保守性の向上が挙げられます。

  1. Unicode正規化の複雑性: Unicodeには、同じ文字が複数の異なるバイト列で表現される「等価性」の問題が存在します。これを解決するために、Unicode正規化形式(Normalization Forms)が定義されています。NFC (Normalization Form C) は合成済み形式、NFD (Normalization Form D) は分解済み形式です。これらの形式間での変換や、文字列の比較、検索などを行う際には、正確な境界の検出が不可欠です。
  2. 境界条件の統一の必要性: コミットメッセージにあるように、NFDでは「a」と「`」のような文字の間に境界を許容する場合があります。これは、結合文字(combining characters)の特性によるものです。しかし、アプリケーションによっては、このような「文字の途中」に境界があることを予期せず、照合や文字列操作で問題を引き起こす可能性があります。このコミットは、ユーザーが直感的に理解しやすい「文字レベル」での境界を強制することで、このような潜在的な問題を回避し、より堅牢な正規化処理を提供することを目指しています。
  3. コードの整理と将来の拡張性: qcflagsメソッドのruneInfoへの移動や、ビットの入れ替え、名前付きフィールドの使用は、コードの論理的な構造を改善し、将来的な機能拡張やパフォーマンス最適化のための準備です。特に、テーブルフォーマットの変更が予定されており、それに先立って内部構造を整理することで、変更作業をスムーズに進める狙いがあります。
  4. 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パッケージの内部実装にいくつかの重要な変更を加えています。

  1. 境界条件の統一と間接参照の削減:

    • 以前はformInfo構造体にboundaryBeforeboundaryAfterという関数ポインタ(boundaryFunc型)が定義されており、正規化形式(NFC, NFDなど)に応じて異なる境界判定ロジックがディスパッチされていました。
    • このコミットでは、boundaryFunc型とformInfo内の対応するフィールドが削除されました。
    • 代わりに、runeInfo型に直接boundaryBefore()boundaryAfter()メソッドが追加されました。これにより、境界判定ロジックがruneInfoにカプセル化され、正規化形式に依存しない統一された判定が可能になります。
    • コミットメッセージにあるように、NFDが「a」と「`」の間に境界を許容するようなケースでも、この変更により「文字レベル」での境界が強制されるようになります。これは、runeInfo.combinesBackward()のチェックによって実現されます。ccc == 0(結合文字でない)かつcombinesBackward()false(後続の文字と結合しない)の場合にのみ境界とみなすことで、より厳密な文字境界を定義しています。
  2. qcflagsメソッドのruneInfoへの移動:

    • 以前はqcInfo型に定義されていたisYesC(), isYesD(), combinesForward(), combinesBackward(), hasDecomposition()などのメソッドが、runeInfo型に移動されました。
    • これにより、runeInfoが文字に関するすべての情報(サイズ、CCC、クイックチェックフラグ、そしてそれらに関連する判定ロジック)を一元的に管理するようになり、コードの凝集度が高まりました。
  3. qcFlagsにおけるYesCYesMaybeビットの入れ替え:

    • qcInfoのビット定義が変更されました。以前はNFC_QC Yes(00), No (01), or Maybe (11)でしたが、NFC_QC Yes(00), No (10), or Maybe (11)に変更されました。
    • 具体的には、maketables.goQCNoの場合にe |= 0x2だったのがe |= 0x4に変更されています。
    • これは、qcInfoのビットマスクと対応するメソッドのロジックにも影響を与え、isYesC()combinesBackward()(以前はisMaybeと同じビットを使用)の判定ロジックが調整されました。この変更は「将来の変更を容易にするため」とされており、おそらく内部的なデータ表現の最適化や、新しい正規化ルールへの対応を見越したものです。
  4. runeInfoの戻り値における名前付きフィールドの使用:

    • runeInfo構造体の初期化において、runeInfo{pos: bn, size: uint8(sz)}のように名前付きフィールドが明示的に使用されるようになりました。
    • これにより、コードの可読性が向上し、各フィールドが何を表しているのかがより明確になります。特に、構造体の定義が変更された場合でも、名前付きフィールドを使用していればコードの変更範囲を限定しやすくなります。
  5. 残存するuint32rune型への置換:

    • composition.goinput.gomaketables.gotables.goなど、複数のファイルでuint32型がrune型に置き換えられました。
    • 例えば、reorderBuffer.decomposeHangul(r uint32)reorderBuffer.decomposeHangul(r rune)に変更され、recompMapmap[uint32]uint32map[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のビット定義(YesCYesMaybeのビット位置)を変更し、それに伴い関連メソッドのロジックを修正。
    • runeInfoboundaryBefore()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に変更。
    • charInfoValuescharInfoSparseValues内のqcInfo関連の定数値を更新(0x30000x5000に、0x33000x5500に、0x38000x5800に変わるなど)。これはYesCYesMaybeビットの入れ替えによるものです。

コアとなるコードの解説

このコミットの核心は、Unicode正規化処理における「境界」の扱いと、関連するデータ構造およびメソッドの整理にあります。

1. 境界条件の統一 (forminfo.go, normalize.go)

以前は、正規化形式(NFC, NFDなど)ごとに異なるboundaryBeforeboundaryAfter関数が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. YesCYesMaybeビットの入れ替えと定数値の変更 (forminfo.go, maketables.go, tables.go)

qcInfoの内部ビット表現が変更されました。特に、NFCのクイックチェックフラグにおけるYesCYesMaybeのビット位置が入れ替えられました。

変更前 (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.goisYesC()メソッドのロジックがi.flags&0x2 == 0からi.flags&0x4 == 0に変更されました。また、maketables.goでクイックチェックフラグを生成する際の定数値(e |= 0x2e |= 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言語の慣習に沿って処理するための基盤を強化するものです。

関連リンク

参考にした情報源リンク