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

[インデックス 13651] ファイルの概要

このコミットは、Go言語の実験的なロケールパッケージ exp/locale/collate 内の maketables.go ファイルに対する変更です。maketables.go は、Unicode Collation Algorithm (UCA) のデフォルト照合要素テーブル (DUCET) などのデータソースから、Go言語の照合パッケージが使用する照合テーブルを生成するためのツールです。このツールは、国際化対応における文字列の正しいソート順序を決定するために不可欠なデータを作成します。

コミット

このコミットは、maketables ツールに新しいテストフラグを追加し、新しく生成された照合テーブルと既存(または以前に生成された)のテーブルを比較できるようにすることを目的としています。これにより、照合データの更新や変更があった際に、その変更が既存の照合ロジックに予期せぬ影響を与えないことを検証するメカニズムが提供されます。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/6918357031df3a7e3373fcb0b28657ead4afc1bb

元コミット内容

exp/locale/collate: Added test flag to maketables tool for comparing newly
against previously generated tables.

R=r
CC=golang-dev
https://golang.org/cl/6441098

変更の背景

Go言語の exp/locale/collate パッケージは、国際的な文字列の照合(ソート順序の決定)を扱うためのものです。この機能は、Unicode Collation Algorithm (UCA) に基づいており、そのデータは定期的に更新される可能性があります。maketables.go ツールは、これらの外部データソースからGo言語のコードが利用できる形式の照合テーブルを生成します。

しかし、新しいUCAデータがリリースされたり、ツールのロジックが変更されたりするたびに、生成されるテーブルが以前のバージョンと互換性があるか、または期待通りの変更が反映されているかを検証する必要があります。手動での比較は非効率的であり、エラーを見落とす可能性があります。

このコミットの背景には、以下のようなニーズがありました。

  1. データの一貫性検証: 新しいUCAデータが取り込まれた際に、生成される照合テーブルが既存の動作を壊さないことを自動的に確認したい。
  2. 回帰テスト: ツールの内部ロジックが変更された場合でも、以前に正しく動作していた照合が引き続き機能することを保証したい。
  3. 開発効率の向上: 開発者が手動でテーブルを比較する手間を省き、自動化されたテストによって迅速にフィードバックを得られるようにしたい。

このテストフラグの導入により、maketables ツール自体が自己検証の機能を持つようになり、照合テーブルの生成プロセスにおける信頼性と保守性が向上しました。

前提知識の解説

このコミットを理解するためには、以下の概念について知っておく必要があります。

  • Go言語の exp パッケージ: Go言語の標準ライブラリには、exp (experimental) というプレフィックスを持つパッケージ群が存在します。これらは、まだ安定版としてリリースされていない、実験的な機能やAPIを提供します。将来的に標準ライブラリに取り込まれる可能性もありますが、APIの変更や削除が行われることもあります。exp/locale/collate もその一つで、国際化対応の照合機能を提供します。

  • ロケール (Locale): ロケールとは、ユーザーの言語、地域、文化的な設定を定義する一連のパラメータのことです。これには、日付や時刻のフォーマット、通貨記号、数値の区切り文字、そして文字列のソート順序(照合)などが含まれます。例えば、ドイツ語ではウムラウト文字のソート順が英語とは異なる場合があります。

  • 照合 (Collation): 照合とは、文字列を特定の言語や文化の規則に従ってソート(並べ替え)したり、比較したりするプロセスです。単純なバイナリ比較とは異なり、照合は文字のアクセント、大文字・小文字、合字(ligature)、および特定の言語に固有の文字の扱いを考慮します。例えば、スウェーデン語では 'å', 'ä', 'ö' が 'z' の後に来るといった規則があります。

  • Unicode Collation Algorithm (UCA): UCAは、Unicode Consortiumによって定義された、国際的な文字列照合のための標準アルゴリズムです。これは、異なる言語やスクリプト間で一貫した照合順序を提供することを目的としています。UCAは、各文字に「照合要素」と呼ばれる重みを割り当てることで機能し、これらの重みを比較することで文字列の順序を決定します。

  • DUCET (Default Unicode Collation Element Table): DUCETは、UCAのデフォルトの照合要素テーブルです。これは、UCAのアルゴリズムで使用される文字ごとの照合要素のリストを提供します。maketables.go ツールは、このDUCETデータ(通常は allkeys.txt というファイルで提供される)を読み込み、Go言語の照合パッケージが利用できる形式に変換します。

  • Go言語の flag パッケージ: Go言語の標準ライブラリである flag パッケージは、コマンドライン引数を解析するための機能を提供します。これにより、プログラムの実行時にユーザーがオプションを指定できるようになります。このコミットでは、--test という新しいブール型フラグが追加されています。

  • bytes.Compare: Go言語の bytes パッケージに含まれる Compare 関数は、2つのバイトスライスを辞書順に比較します。結果は、最初のスライスが2番目のスライスより小さい場合は負の整数、等しい場合は0、大きい場合は正の整数となります。照合キーの比較に利用されます。

  • sort.Strings: Go言語の sort パッケージに含まれる Strings 関数は、文字列のスライスを辞書順にソートします。このコミットでは、テスト対象の文字列を効率的に管理するために使用されています。

技術的詳細

このコミットの主要な技術的変更点は、maketables.go ツールに新しいテストモードを導入したことです。

  1. --test フラグの追加: flag パッケージを使用して、--test という新しいブール型コマンドラインフラグが追加されました。このフラグが true に設定されると、ツールは照合テーブルの生成ではなく、テストモードで動作します。

    var test = flag.Bool("test",
    	false,
    	"test existing tables; can be used to compare web data with package data")
    
  2. stringSet 型の導入: テスト対象となる文字列(ルーン)を効率的に収集・管理するために、stringSet という新しい型が定義されました。これは内部的に文字列のスライス []string を持ち、add メソッドで文字列を追加し、compact メソッドで重複を排除しソートします。values メソッドはソートされ重複のない文字列のスライスを返します。

    type stringSet struct {
    	set []string
    }
    
    func (ss *stringSet) add(s string) {
    	ss.set = append(ss.set, s)
    }
    
    func (ss *stringSet) values() []string {
    	ss.compact()
    	return ss.set
    }
    
    func (ss *stringSet) compact() {
    	a := ss.set
    	sort.Strings(a) // 文字列をソート
    	k := 0
    	for i := 1; i < len(a); i++ {
    		if a[k] != a[i] {
    			a[k+1] = a[i]
    			k++
    		}
    	}
    	ss.set = a[:k+1] // 重複を排除
    }
    
  3. parseUCA でのテスト入力の収集: parseUCA 関数は、DUCETデータから照合ルールを解析する際に、--test フラグが有効な場合、解析された左辺(lhs、照合対象の文字列)をグローバルな testInput (stringSet のインスタンス) に追加するように変更されました。これにより、DUCETデータに含まれるすべての照合対象文字列がテストセットに自動的に含まれます。

    			if *test {
    				testInput.add(string(lhs))
    			}
    
  4. testCollator 関数の実装: この関数は、新しく生成された collate.Collator インスタンス (c) が、既存の collate.Root コレーター (c0) と同じ照合キーを生成するかどうかを検証します。

    • 広範囲のUnicodeルーンポイント(0 から 0x30000、および 0xE0000 から 0xF0000)を testInput に追加します。これにより、DUCETデータに含まれる文字列だけでなく、一般的なUnicode文字もテスト対象に含まれ、網羅性が高まります。
    • testInput.values() から取得した各文字列に対し、c0.KeyFromStringc.KeyFromString を呼び出して照合キーを生成します。
    • 生成された2つのキー(k0k)を bytes.Compare で比較します。
    • キーが異なる場合、エラーメッセージを出力してプログラムを終了します。
    • すべてのテストが成功した場合、「PASS」と出力します。
    func testCollator(c *collate.Collator) {
    	c0 := collate.Root // 既存のRootコレーター
    
    	buf := collate.Buffer{}
    
    	// 広範囲のUnicodeルーンポイントをテストセットに追加
    	for i := rune(0); i < 0x30000; i++ {
    		testInput.add(string(i))
    	}
    	for i := rune(0xE0000); i < 0xF0000; i++ {
    		testInput.add(string(i))
    	}
    
    	for _, str := range testInput.values() {
    		k0 := c0.KeyFromString(&buf, str) // Rootコレーターでキーを生成
    		k := c.KeyFromString(&buf, str)   // 新しいコレーターでキーを生成
    		if bytes.Compare(k0, k) != 0 {    // キーを比較
    			failOnError(fmt.Errorf("test:%U: keys differ (%x vs %x)", []rune(str), k0, k))
    		}
    		buf.ResetKeys()
    	}
    	fmt.Println("PASS")
    }
    
  5. main 関数の条件分岐: main 関数は、--test フラグの値に基づいて動作を切り替えるようになりました。

    • *testtrue の場合、testCollator(c) を呼び出してテストを実行します。
    • *testfalse の場合、以前と同様に生成された照合テーブルを標準出力に書き出します。
    	if *test {
    		testCollator(c)
    	} else {
    		// 既存のテーブル生成ロジック
    		fmt.Println("// Generated by running")
    		fmt.Printf("//  maketables --ducet=%s\\n", *ducet)
    		fmt.Println("// DO NOT EDIT")
    		fmt.Println("// TODO: implement more compact representation for sparse blocks.")
    		fmt.Println("")
    		fmt.Println("package collate")
    		fmt.Println("")
    		fmt.Println(`import "exp/norm"`)
    		fmt.Println("")
    
    		printCollators(c)
    
    		_, err = b.Print(os.Stdout)
    		failOnError(err)
    	}
    

これらの変更により、maketables ツールは、生成された照合テーブルの品質と一貫性を自動的に検証する強力なメカニズムを獲得しました。

コアとなるコードの変更箇所

--- a/src/pkg/exp/locale/collate/maketables.go
+++ b/src/pkg/exp/locale/collate/maketables.go
@@ -11,6 +11,7 @@ package main
 
 import (
 	"bufio"
+	"bytes"
 	"exp/locale/collate"
 	"exp/locale/collate/build"
 	"flag"
@@ -21,6 +22,7 @@ import (
 	"os"
 	"path"
 	"regexp"
+	"sort"
 	"strconv"
 	"strings"
 	"unicode"
@@ -29,6 +31,9 @@ import (
 var ducet = flag.String("ducet",
 	"http://unicode.org/Public/UCA/"+unicode.Version+"/allkeys.txt",
 	"URL of the Default Unicode Collation Element Table (DUCET).")
+var test = flag.Bool("test",
+	false,
+	"test existing tables; can be used to compare web data with package data")
 var localFiles = flag.Bool("local",
 	false,
 	"data files have been copied to the current directory; for debugging only")
@@ -124,6 +129,9 @@ func parseUCA(builder *build.Builder) {
 			if len(part[1]) < n+3 || part[1][n+1] != '#' {
 				log.Fatalf("%d: expected comment; found %s", i, part[1][n:])
 			}
+			if *test {
+				testInput.add(string(lhs))
+			}
 			failOnError(builder.Add(lhs, rhs, vars))
 		}
 	}
@@ -137,6 +145,59 @@ func convHex(line int, s string) int {
 	return int(r)
 }
 
+var testInput = stringSet{}
+
+type stringSet struct {
+	set []string
+}
+
+func (ss *stringSet) add(s string) {
+	ss.set = append(ss.set, s)
+}
+
+func (ss *stringSet) values() []string {
+	ss.compact()
+	return ss.set
+}
+
+func (ss *stringSet) compact() {
+	a := ss.set
+	sort.Strings(a)
+	k := 0
+	for i := 1; i < len(a); i++ {
+		if a[k] != a[i] {
+			a[k+1] = a[i]
+			k++
+		}
+	}
+	ss.set = a[:k+1]
+}
+
+func testCollator(c *collate.Collator) {
+	c0 := collate.Root
+
+	// iterator over all characters for all locales and check
+	// whether Key is equal.
+	buf := collate.Buffer{}
+
+	// Add all common and not too uncommon runes to the test set.
+	for i := rune(0); i < 0x30000; i++ {
+		testInput.add(string(i))
+	}
+	for i := rune(0xE0000); i < 0xF0000; i++ {
+		testInput.add(string(i))
+	}
+	for _, str := range testInput.values() {
+		k0 := c0.KeyFromString(&buf, str)
+		k := c.KeyFromString(&buf, str)
+		if bytes.Compare(k0, k) != 0 {
+			failOnError(fmt.Errorf("test:%U: keys differ (%x vs %x)", []rune(str), k0, k))
+		}
+		buf.ResetKeys()
+	}
+	fmt.Println("PASS")
+}
+
 // TODO: move this functionality to exp/locale/collate/build.
 func printCollators(c *collate.Collator) {
 	const name = "Root"
@@ -157,18 +222,22 @@ func main() {
 	c, err := b.Build()
 	failOnError(err)
 
-	fmt.Println("// Generated by running")
-	fmt.Printf("//  maketables --ducet=%s\\n", *ducet)
-	fmt.Println("// DO NOT EDIT")
-	fmt.Println("// TODO: implement more compact representation for sparse blocks.")
-	fmt.Println("")
-	fmt.Println("package collate")
-	fmt.Println("")
-	fmt.Println(`import "exp/norm"`)
-	fmt.Println("")
+	if *test {
+		testCollator(c)
+	} else {
+		fmt.Println("// Generated by running")
+		fmt.Printf("//  maketables --ducet=%s\\n", *ducet)
+		fmt.Println("// DO NOT EDIT")
+		fmt.Println("// TODO: implement more compact representation for sparse blocks.")
+		fmt.Println("")
+		fmt.Println("package collate")
+		fmt.Println("")
+		fmt.Println(`import "exp/norm"`)
+		fmt.Println("")
 
-	printCollators(c)
+		printCollators(c)
 
-	_, err = b.Print(os.Stdout)
-	failOnError(err)
+		_, err = b.Print(os.Stdout)
+		failOnError(err)
+	}
 }

コアとなるコードの解説

上記の差分は、maketables.go にテスト機能を追加するための主要な変更を示しています。

  1. import 文の追加:

    • "bytes": バイトスライスを比較するために bytes.Compare 関数を使用するために追加されました。
    • "sort": 文字列のスライスをソートするために sort.Strings 関数を使用するために追加されました。
  2. test フラグの定義:

    var test = flag.Bool("test",
    	false,
    	"test existing tables; can be used to compare web data with package data")
    

    flag.Bool を使用して、--test という名前のブール型コマンドラインフラグを定義しています。デフォルト値は false で、説明として「既存のテーブルをテストする; ウェブデータとパッケージデータを比較するために使用できる」と記述されています。

  3. parseUCA 関数内の条件付き testInput.add:

    			if *test {
    				testInput.add(string(lhs))
    			}
    

    parseUCA 関数は、DUCETデータから照合ルールを解析するループ内で実行されます。*test (つまり --test フラグが true の場合) の条件が満たされると、解析された照合対象の文字列 (lhs) が testInput というグローバルな stringSet インスタンスに追加されます。これにより、DUCETデータに含まれるすべての照合対象文字列がテスト対象として収集されます。

  4. testInputstringSet の定義:

    var testInput = stringSet{}
    
    type stringSet struct {
    	set []string
    }
    
    func (ss *stringSet) add(s string) {
    	ss.set = append(ss.set, s)
    }
    
    func (ss *stringSet) values() []string {
    	ss.compact()
    	return ss.set
    }
    
    func (ss *stringSet) compact() {
    	a := ss.set
    	sort.Strings(a) // スライスをソート
    	k := 0
    	for i := 1; i < len(a); i++ {
    		if a[k] != a[i] {
    			a[k+1] = a[i]
    			k++
    		}
    	}
    	ss.set = a[:k+1] // 重複を排除
    }
    

    testInputstringSet 型の変数として宣言され、テスト対象の文字列を保持します。stringSet は、文字列のスライス set を内部に持ちます。

    • add(s string): スライスに文字列 s を追加します。
    • values() []string: compact() を呼び出してスライスをソートし、重複を排除した後に、そのスライスを返します。
    • compact(): 内部のスライスをソートし、隣接する重複要素を削除することで、ユニークな文字列のセットを作成します。
  5. testCollator 関数の実装:

    func testCollator(c *collate.Collator) {
    	c0 := collate.Root // 既存のRootコレーターを取得
    
    	buf := collate.Buffer{} // キー生成用の一時バッファ
    
    	// 一般的なUnicodeルーンポイントをテストセットに追加
    	for i := rune(0); i < 0x30000; i++ {
    		testInput.add(string(i))
    	}
    	for i := rune(0xE0000); i < 0xF0000; i++ {
    		testInput.add(string(i))
    	}
    
    	for _, str := range testInput.values() { // 収集されたすべての文字列をイテレート
    		k0 := c0.KeyFromString(&buf, str) // Rootコレーターでキーを生成
    		k := c.KeyFromString(&buf, str)   // 新しいコレーターでキーを生成
    		if bytes.Compare(k0, k) != 0 {    // 2つのキーを比較
    			failOnError(fmt.Errorf("test:%U: keys differ (%x vs %x)", []rune(str), k0, k))
    		}
    		buf.ResetKeys() // バッファをリセット
    	}
    	fmt.Println("PASS") // すべてのテストが成功した場合
    }
    

    この関数は、引数として渡された新しいコレーター c をテストします。

    • collate.Root は、Go言語の collate パッケージが提供するデフォルトの照合器であり、比較の基準となります。
    • 広範囲のUnicodeルーンポイント(基本多言語面の一部と追加多言語面の一部)を testInput に追加することで、テストの網羅性を高めています。
    • testInput.values() から取得した各文字列について、collate.Root と新しいコレーターの両方で照合キーを生成します。
    • bytes.Compare を使用して、生成された2つのキーが同一であるかを検証します。キーが異なる場合、エラーメッセージを出力してプログラムを終了します。これは、新しいコレーターが collate.Root と異なる照合順序を生成していることを意味します。
    • すべての文字列に対してキーが一致した場合、「PASS」と出力し、テストが成功したことを示します。
  6. main 関数内の条件分岐:

    	if *test {
    		testCollator(c)
    	} else {
    		// ... 既存のテーブル生成ロジック ...
    	}
    

    main 関数は、--test フラグが設定されているかどうか (*test の値) に基づいて、プログラムの実行パスを分岐させます。

    • *testtrue の場合、testCollator(c) が呼び出され、照合キーの比較テストが実行されます。
    • *testfalse の場合、元の maketables.go の機能である、DUCETデータから照合テーブルを生成し、Goのソースコード形式で標準出力に書き出す処理が実行されます。

これらの変更により、maketables.go は、照合テーブルの生成と同時に、その正確性を検証する機能を持つようになりました。

関連リンク

  • Go言語の exp/locale/collate パッケージ:
    • Go言語の公式ドキュメント (当時のバージョンに基づく): https://pkg.go.dev/exp/locale/collate (現在のGoのバージョンでは golang.org/x/text/collate に移行している可能性がありますが、当時の exp パッケージの概念を理解する上で重要です。)
  • Unicode Collation Algorithm (UCA):
  • Default Unicode Collation Element Table (DUCET):
  • Go言語の flag パッケージ:
  • Go言語の bytes パッケージ:
  • Go言語の sort パッケージ:

参考にした情報源リンク

  • Go言語の公式ドキュメント (pkg.go.dev)
  • Unicode Consortium のウェブサイト (unicode.org)
  • Go言語のソースコードリポジトリ (GitHub)
  • 一般的なプログラミングにおける国際化 (i18n) とローカライゼーション (l10n) の概念に関する情報
  • Go言語の flagbytessort パッケージの利用例に関する情報I have generated the detailed explanation based on the provided commit information and the required structure. I will now output it to standard output.
# [インデックス 13651] ファイルの概要

このコミットは、Go言語の実験的なロケールパッケージ `exp/locale/collate` 内の `maketables.go` ファイルに対する変更です。`maketables.go` は、Unicode Collation Algorithm (UCA) のデフォルト照合要素テーブル (DUCET) などのデータソースから、Go言語の照合パッケージが使用する照合テーブルを生成するためのツールです。このツールは、国際化対応における文字列の正しいソート順序を決定するために不可欠なデータを作成します。

## コミット

このコミットは、`maketables` ツールに新しいテストフラグを追加し、新しく生成された照合テーブルと既存(または以前に生成された)のテーブルを比較できるようにすることを目的としています。これにより、照合データの更新や変更があった際に、その変更が既存の照合ロジックに予期せぬ影響を与えないことを検証するメカニズムが提供されます。

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/6918357031df3a7e3373fcb0b28657ead4afc1bb](https://github.com/golang/go/commit/6918357031df3a7e3373fcb0b28657ead4afc1bb)

## 元コミット内容

exp/locale/collate: Added test flag to maketables tool for comparing newly against previously generated tables.

R=r CC=golang-dev https://golang.org/cl/6441098


## 変更の背景

Go言語の `exp/locale/collate` パッケージは、国際的な文字列の照合(ソート順序の決定)を扱うためのものです。この機能は、Unicode Collation Algorithm (UCA) に基づいており、そのデータは定期的に更新される可能性があります。`maketables.go` ツールは、これらの外部データソースからGo言語のコードが利用できる形式の照合テーブルを生成します。

しかし、新しいUCAデータがリリースされたり、ツールのロジックが変更されたりするたびに、生成されるテーブルが以前のバージョンと互換性があるか、または期待通りの変更が反映されているかを検証する必要があります。手動での比較は非効率的であり、エラーを見落とす可能性があります。

このコミットの背景には、以下のようなニーズがありました。

1.  **データの一貫性検証**: 新しいUCAデータが取り込まれた際に、生成される照合テーブルが既存の動作を壊さないことを自動的に確認したい。
2.  **回帰テスト**: ツールの内部ロジックが変更された場合でも、以前に正しく動作していた照合が引き続き機能することを保証したい。
3.  **開発効率の向上**: 開発者が手動でテーブルを比較する手間を省き、自動化されたテストによって迅速にフィードバックを得られるようにしたい。

このテストフラグの導入により、`maketables` ツール自体が自己検証の機能を持つようになり、照合テーブルの生成プロセスにおける信頼性と保守性が向上しました。

## 前提知識の解説

このコミットを理解するためには、以下の概念について知っておく必要があります。

*   **Go言語の `exp` パッケージ**:
    Go言語の標準ライブラリには、`exp` (experimental) というプレフィックスを持つパッケージ群が存在します。これらは、まだ安定版としてリリースされていない、実験的な機能やAPIを提供します。将来的に標準ライブラリに取り込まれる可能性もありますが、APIの変更や削除が行われることもあります。`exp/locale/collate` もその一つで、国際化対応の照合機能を提供します。

*   **ロケール (Locale)**:
    ロケールとは、ユーザーの言語、地域、文化的な設定を定義する一連のパラメータのことです。これには、日付や時刻のフォーマット、通貨記号、数値の区切り文字、そして文字列のソート順序(照合)などが含まれます。例えば、ドイツ語ではウムラウト文字のソート順が英語とは異なる場合があります。

*   **照合 (Collation)**:
    照合とは、文字列を特定の言語や文化の規則に従ってソート(並べ替え)したり、比較したりするプロセスです。単純なバイナリ比較とは異なり、照合は文字のアクセント、大文字・小文字、合字(ligature)、および特定の言語に固有の文字の扱いを考慮します。例えば、スウェーデン語では 'å', 'ä', 'ö' が 'z' の後に来るといった規則があります。

*   **Unicode Collation Algorithm (UCA)**:
    UCAは、Unicode Consortiumによって定義された、国際的な文字列照合のための標準アルゴリズムです。これは、異なる言語やスクリプト間で一貫した照合順序を提供することを目的としています。UCAは、各文字に「照合要素」と呼ばれる重みを割り当てることで機能し、これらの重みを比較することで文字列の順序を決定します。

*   **DUCET (Default Unicode Collation Element Table)**:
    DUCETは、UCAのデフォルトの照合要素テーブルです。これは、UCAのアルゴリズムで使用される文字ごとの照合要素のリストを提供します。`maketables.go` ツールは、このDUCETデータ(通常は `allkeys.txt` というファイルで提供される)を読み込み、Go言語の照合パッケージが利用できる形式に変換します。

*   **Go言語の `flag` パッケージ**:
    Go言語の標準ライブラリである `flag` パッケージは、コマンドライン引数を解析するための機能を提供します。これにより、プログラムの実行時にユーザーがオプションを指定できるようになります。このコミットでは、`--test` という新しいブール型フラグが追加されています。

*   **`bytes.Compare`**:
    Go言語の `bytes` パッケージに含まれる `Compare` 関数は、2つのバイトスライスを辞書順に比較します。結果は、最初のスライスが2番目のスライスより小さい場合は負の整数、等しい場合は0、大きい場合は正の整数となります。照合キーの比較に利用されます。

*   **`sort.Strings`**:
    Go言語の `sort` パッケージに含まれる `Strings` 関数は、文字列のスライスを辞書順にソートします。このコミットでは、テスト対象の文字列を効率的に管理するために使用されています。

## 技術的詳細

このコミットの主要な技術的変更点は、`maketables.go` ツールに新しいテストモードを導入したことです。

1.  **`--test` フラグの追加**:
    `flag` パッケージを使用して、`--test` という新しいブール型コマンドラインフラグが追加されました。このフラグが `true` に設定されると、ツールは照合テーブルの生成ではなく、テストモードで動作します。

    ```go
    var test = flag.Bool("test",
    	false,
    	"test existing tables; can be used to compare web data with package data")
    ```

2.  **`stringSet` 型の導入**:
    テスト対象となる文字列(ルーン)を効率的に収集・管理するために、`stringSet` という新しい型が定義されました。これは内部的に文字列のスライス `[]string` を持ち、`add` メソッドで文字列を追加し、`compact` メソッドで重複を排除しソートします。`values` メソッドはソートされ重複のない文字列のスライスを返します。

    ```go
    type stringSet struct {
    	set []string
    }

    func (ss *stringSet) add(s string) {
    	ss.set = append(ss.set, s)
    }

    func (ss *stringSet) values() []string {
    	ss.compact()
    	return ss.set
    }

    func (ss *stringSet) compact() {
    	a := ss.set
    	sort.Strings(a) // 文字列をソート
    	k := 0
    	for i := 1; i < len(a); i++ {
    		if a[k] != a[i] {
    			a[k+1] = a[i]
    			k++
    		}
    	}
    	ss.set = a[:k+1] // 重複を排除
    }
    ```

3.  **`parseUCA` でのテスト入力の収集**:
    `parseUCA` 関数は、DUCETデータから照合ルールを解析する際に、`--test` フラグが有効な場合、解析された左辺(`lhs`、照合対象の文字列)をグローバルな `testInput` (`stringSet` のインスタンス) に追加するように変更されました。これにより、DUCETデータに含まれるすべての照合対象文字列がテストセットに自動的に含まれます。

    ```go
    			if *test {
    				testInput.add(string(lhs))
    			}
    ```

4.  **`testCollator` 関数の実装**:
    この関数は、新しく生成された `collate.Collator` インスタンス (`c`) が、既存の `collate.Root` コレーター (`c0`) と同じ照合キーを生成するかどうかを検証します。

    *   広範囲のUnicodeルーンポイント(`0` から `0x30000`、および `0xE0000` から `0xF0000`)を `testInput` に追加します。これにより、DUCETデータに含まれる文字列だけでなく、一般的なUnicode文字もテスト対象に含まれ、網羅性が高まります。
    *   `testInput.values()` から取得した各文字列に対し、`c0.KeyFromString` と `c.KeyFromString` を呼び出して照合キーを生成します。
    *   生成された2つのキー(`k0` と `k`)を `bytes.Compare` で比較します。
    *   キーが異なる場合、エラーメッセージを出力してプログラムを終了します。
    *   すべてのテストが成功した場合、「PASS」と出力します。

    ```go
    func testCollator(c *collate.Collator) {
    	c0 := collate.Root // 既存のRootコレーター

    	buf := collate.Buffer{}

    	// Add all common and not too uncommon runes to the test set.
    	for i := rune(0); i < 0x30000; i++ {
    		testInput.add(string(i))
    	}
    	for i := rune(0xE0000); i < 0xF0000; i++ {
    		testInput.add(string(i))
    	}

    	for _, str := range testInput.values() {
    		k0 := c0.KeyFromString(&buf, str) // Rootコレーターでキーを生成
    		k := c.KeyFromString(&buf, str)   // 新しいコレーターでキーを生成
    		if bytes.Compare(k0, k) != 0 {    // キーを比較
    			failOnError(fmt.Errorf("test:%U: keys differ (%x vs %x)", []rune(str), k0, k))
    		}
    		buf.ResetKeys()
    	}
    	fmt.Println("PASS")
    }
    ```

5.  **`main` 関数の条件分岐**:
    `main` 関数は、`--test` フラグの値に基づいて動作を切り替えるようになりました。
    *   `*test` が `true` の場合、`testCollator(c)` を呼び出してテストを実行します。
    *   `*test` が `false` の場合、以前と同様に生成された照合テーブルを標準出力に書き出します。

    ```go
    	if *test {
    		testCollator(c)
    	} else {
    		// 既存のテーブル生成ロジック
    		fmt.Println("// Generated by running")
    		fmt.Printf("//  maketables --ducet=%s\\n", *ducet)
    		fmt.Println("// DO NOT EDIT")
    		fmt.Println("// TODO: implement more compact representation for sparse blocks.")
    		fmt.Println("")
    		fmt.Println("package collate")
    		fmt.Println("")
    		fmt.Println(`import "exp/norm"`)
    		fmt.Println("")

    		printCollators(c)

    		_, err = b.Print(os.Stdout)
    		failOnError(err)
    	}
    ```

これらの変更により、`maketables` ツールは、生成された照合テーブルの品質と一貫性を自動的に検証する強力なメカニズムを獲得しました。

## コアとなるコードの変更箇所

```diff
--- a/src/pkg/exp/locale/collate/maketables.go
+++ b/src/pkg/exp/locale/collate/maketables.go
@@ -11,6 +11,7 @@ package main
 
 import (
 	"bufio"
+	"bytes"
 	"exp/locale/collate"
 	"exp/locale/collate/build"
 	"flag"
@@ -21,6 +22,7 @@ import (
 	"os"
 	"path"
 	"regexp"
+	"sort"
 	"strconv"
 	"strings"
 	"unicode"
@@ -29,6 +31,9 @@ import (
 var ducet = flag.String("ducet",
 	"http://unicode.org/Public/UCA/"+unicode.Version+"/allkeys.txt",
 	"URL of the Default Unicode Collation Element Table (DUCET).")
+var test = flag.Bool("test",
+	false,
+	"test existing tables; can be used to compare web data with package data")
 var localFiles = flag.Bool("local",
 	false,
 	"data files have been copied to the current directory; for debugging only")
@@ -124,6 +129,9 @@ func parseUCA(builder *build.Builder) {
 			if len(part[1]) < n+3 || part[1][n+1] != '#' {
 				log.Fatalf("%d: expected comment; found %s", i, part[1][n:])
 			}
+			if *test {
+				testInput.add(string(lhs))
+			}
 			failOnError(builder.Add(lhs, rhs, vars))
 		}
 	}
@@ -137,6 +145,59 @@ func convHex(line int, s string) int {
 	return int(r)
 }
 
+var testInput = stringSet{}
+
+type stringSet struct {
+	set []string
+}
+
+func (ss *stringSet) add(s string) {
+	ss.set = append(ss.set, s)
+}
+
+func (ss *stringSet) values() []string {
+	ss.compact()
+	return ss.set
+}
+
+func (ss *stringSet) compact() {
+	a := ss.set
+	sort.Strings(a)
+	k := 0
+	for i := 1; i < len(a); i++ {
+		if a[k] != a[i] {
+			a[k+1] = a[i]
+			k++
+		}
+	}
+	ss.set = a[:k+1]
+}
+
+func testCollator(c *collate.Collator) {
+	c0 := collate.Root
+
+	// iterator over all characters for all locales and check
+	// whether Key is equal.
+	buf := collate.Buffer{}
+
+	// Add all common and not too uncommon runes to the test set.
+	for i := rune(0); i < 0x30000; i++ {
+		testInput.add(string(i))
+	}
+	for i := rune(0xE0000); i < 0xF0000; i++ {
+		testInput.add(string(i))
+	}
+	for _, str := range testInput.values() {
+		k0 := c0.KeyFromString(&buf, str)
+		k := c.KeyFromString(&buf, str)
+		if bytes.Compare(k0, k) != 0 {
+			failOnError(fmt.Errorf("test:%U: keys differ (%x vs %x)", []rune(str), k0, k))
+		}
+		buf.ResetKeys()
+	}
+	fmt.Println("PASS")
+}
+
 // TODO: move this functionality to exp/locale/collate/build.
 func printCollators(c *collate.Collator) {
 	const name = "Root"
@@ -157,18 +227,22 @@ func main() {
 	c, err := b.Build()
 	failOnError(err)
 
-	fmt.Println("// Generated by running")
-	fmt.Printf("//  maketables --ducet=%s\\n", *ducet)
-	fmt.Println("// DO NOT EDIT")
-	fmt.Println("// TODO: implement more compact representation for sparse blocks.")
-	fmt.Println("")
-	fmt.Println("package collate")
-	fmt.Println("")
-	fmt.Println(`import "exp/norm"`)
-	fmt.Println("")
+	if *test {
+		testCollator(c)
+	} else {
+		fmt.Println("// Generated by running")
+		fmt.Printf("//  maketables --ducet=%s\\n", *ducet)
+		fmt.Println("// DO NOT EDIT")
+		fmt.Println("// TODO: implement more compact representation for sparse blocks.")
+		fmt.Println("")
+		fmt.Println("package collate")
+		fmt.Println("")
+		fmt.Println(`import "exp/norm"`)
+		fmt.Println("")
 
-	printCollators(c)
+		printCollators(c)
 
-	_, err = b.Print(os.Stdout)
-	failOnError(err)
+		_, err = b.Print(os.Stdout)
+		failOnError(err)
+	}
 }

コアとなるコードの解説

上記の差分は、maketables.go にテスト機能を追加するための主要な変更を示しています。

  1. import 文の追加:

    • "bytes": バイトスライスを比較するために bytes.Compare 関数を使用するために追加されました。
    • "sort": 文字列のスライスをソートするために sort.Strings 関数を使用するために追加されました。
  2. test フラグの定義:

    var test = flag.Bool("test",
    	false,
    	"test existing tables; can be used to compare web data with package data")
    

    flag.Bool を使用して、--test という名前のブール型コマンドラインフラグを定義しています。デフォルト値は false で、説明として「既存のテーブルをテストする; ウェブデータとパッケージデータを比較するために使用できる」と記述されています。

  3. parseUCA 関数内の条件付き testInput.add:

    			if *test {
    				testInput.add(string(lhs))
    			}
    

    parseUCA 関数は、DUCETデータから照合ルールを解析するループ内で実行されます。*test (つまり --test フラグが true の場合) の条件が満たされると、解析された照合対象の文字列 (lhs) が testInput というグローバルな stringSet インスタンスに追加されます。これにより、DUCETデータに含まれるすべての照合対象文字列がテスト対象として収集されます。

  4. testInputstringSet の定義:

    var testInput = stringSet{}
    
    type stringSet struct {
    	set []string
    }
    
    func (ss *stringSet) add(s string) {
    	ss.set = append(ss.set, s)
    }
    
    func (ss *stringSet) values() []string {
    	ss.compact()
    	return ss.set
    }
    
    func (ss *stringSet) compact() {
    	a := ss.set
    	sort.Strings(a) // スライスをソート
    	k := 0
    	for i := 1; i < len(a); i++ {
    		if a[k] != a[i] {
    			a[k+1] = a[i]
    			k++
    		}
    	}
    	ss.set = a[:k+1] // 重複を排除
    }
    

    testInputstringSet 型の変数として宣言され、テスト対象の文字列を保持します。stringSet は、文字列のスライス set を内部に持ちます。

    • add(s string): スライスに文字列 s を追加します。
    • values() []string: compact() を呼び出してスライスをソートし、重複を排除した後に、そのスライスを返します。
    • compact(): 内部のスライスをソートし、隣接する重複要素を削除することで、ユニークな文字列のセットを作成します。
  5. testCollator 関数の実装:

    func testCollator(c *collate.Collator) {
    	c0 := collate.Root // 既存のRootコレーターを取得
    
    	buf := collate.Buffer{} // キー生成用の一時バッファ
    
    	// 一般的なUnicodeルーンポイントをテストセットに追加
    	for i := rune(0); i < 0x30000; i++ {
    		testInput.add(string(i))
    	}
    	for i := rune(0xE0000); i < 0xF0000; i++ {
    		testInput.add(string(i))
    	}
    
    	for _, str := range testInput.values() { // 収集されたすべての文字列をイテレート
    		k0 := c0.KeyFromString(&buf, str) // Rootコレーターでキーを生成
    		k := c.KeyFromString(&buf, str)   // 新しいコレーターでキーを生成
    		if bytes.Compare(k0, k) != 0 {    // 2つのキーを比較
    			failOnError(fmt.Errorf("test:%U: keys differ (%x vs %x)", []rune(str), k0, k))
    		}
    		buf.ResetKeys() // バッファをリセット
    	}
    	fmt.Println("PASS") // すべてのテストが成功した場合
    }
    

    この関数は、引数として渡された新しいコレーター c をテストします。

    • collate.Root は、Go言語の collate パッケージが提供するデフォルトの照合器であり、比較の基準となります。
    • 広範囲のUnicodeルーンポイント(基本多言語面の一部と追加多言語面の一部)を testInput に追加することで、テストの網羅性を高めています。
    • testInput.values() から取得した各文字列について、collate.Root と新しいコレーターの両方で照合キーを生成します。
    • bytes.Compare を使用して、生成された2つのキーが同一であるかを検証します。キーが異なる場合、エラーメッセージを出力してプログラムを終了します。これは、新しいコレーターが collate.Root と異なる照合順序を生成していることを意味します。
    • すべての文字列に対してキーが一致した場合、「PASS」と出力し、テストが成功したことを示します。
  6. main 関数内の条件分岐:

    	if *test {
    		testCollator(c)
    	} else {
    		// ... 既存のテーブル生成ロジック ...
    	}
    

    main 関数は、--test フラグが設定されているかどうか (*test の値) に基づいて、プログラムの実行パスを分岐させます。

    • *testtrue の場合、testCollator(c) が呼び出され、照合キーの比較テストが実行されます。
    • *testfalse の場合、元の maketables.go の機能である、DUCETデータから照合テーブルを生成し、Goのソースコード形式で標準出力に書き出す処理が実行されます。

これらの変更により、maketables.go は、照合テーブルの生成と同時に、その正確性を検証する機能を持つようになりました。

関連リンク

  • Go言語の exp/locale/collate パッケージ:
    • Go言語の公式ドキュメント (当時のバージョンに基づく): https://pkg.go.dev/exp/locale/collate (現在のGoのバージョンでは golang.org/x/text/collate に移行している可能性がありますが、当時の exp パッケージの概念を理解する上で重要です。)
  • Unicode Collation Algorithm (UCA):
  • Default Unicode Collation Element Table (DUCET):
  • Go言語の flag パッケージ:
  • Go言語の bytes パッケージ:
  • Go言語の sort パッケージ:

参考にした情報源リンク

  • Go言語の公式ドキュメント (pkg.go.dev)
  • Unicode Consortium のウェブサイト (unicode.org)
  • Go言語のソースコードリポジトリ (GitHub)
  • 一般的なプログラミングにおける国際化 (i18n) とローカライゼーション (l10n) の概念に関する情報
  • Go言語の flagbytessort パッケージの利用例に関する情報