[インデックス 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データがリリースされたり、ツールのロジックが変更されたりするたびに、生成されるテーブルが以前のバージョンと互換性があるか、または期待通りの変更が反映されているかを検証する必要があります。手動での比較は非効率的であり、エラーを見落とす可能性があります。
このコミットの背景には、以下のようなニーズがありました。
- データの一貫性検証: 新しいUCAデータが取り込まれた際に、生成される照合テーブルが既存の動作を壊さないことを自動的に確認したい。
- 回帰テスト: ツールの内部ロジックが変更された場合でも、以前に正しく動作していた照合が引き続き機能することを保証したい。
- 開発効率の向上: 開発者が手動でテーブルを比較する手間を省き、自動化されたテストによって迅速にフィードバックを得られるようにしたい。
このテストフラグの導入により、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
ツールに新しいテストモードを導入したことです。
-
--test
フラグの追加:flag
パッケージを使用して、--test
という新しいブール型コマンドラインフラグが追加されました。このフラグがtrue
に設定されると、ツールは照合テーブルの生成ではなく、テストモードで動作します。var test = flag.Bool("test", false, "test existing tables; can be used to compare web data with package data")
-
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] // 重複を排除 }
-
parseUCA
でのテスト入力の収集:parseUCA
関数は、DUCETデータから照合ルールを解析する際に、--test
フラグが有効な場合、解析された左辺(lhs
、照合対象の文字列)をグローバルなtestInput
(stringSet
のインスタンス) に追加するように変更されました。これにより、DUCETデータに含まれるすべての照合対象文字列がテストセットに自動的に含まれます。if *test { testInput.add(string(lhs)) }
-
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」と出力します。
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") }
- 広範囲のUnicodeルーンポイント(
-
main
関数の条件分岐:main
関数は、--test
フラグの値に基づいて動作を切り替えるようになりました。*test
がtrue
の場合、testCollator(c)
を呼び出してテストを実行します。*test
がfalse
の場合、以前と同様に生成された照合テーブルを標準出力に書き出します。
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
にテスト機能を追加するための主要な変更を示しています。
-
import
文の追加:"bytes"
: バイトスライスを比較するためにbytes.Compare
関数を使用するために追加されました。"sort"
: 文字列のスライスをソートするためにsort.Strings
関数を使用するために追加されました。
-
test
フラグの定義:var test = flag.Bool("test", false, "test existing tables; can be used to compare web data with package data")
flag.Bool
を使用して、--test
という名前のブール型コマンドラインフラグを定義しています。デフォルト値はfalse
で、説明として「既存のテーブルをテストする; ウェブデータとパッケージデータを比較するために使用できる」と記述されています。 -
parseUCA
関数内の条件付きtestInput.add
:if *test { testInput.add(string(lhs)) }
parseUCA
関数は、DUCETデータから照合ルールを解析するループ内で実行されます。*test
(つまり--test
フラグがtrue
の場合) の条件が満たされると、解析された照合対象の文字列 (lhs
) がtestInput
というグローバルなstringSet
インスタンスに追加されます。これにより、DUCETデータに含まれるすべての照合対象文字列がテスト対象として収集されます。 -
testInput
とstringSet
の定義: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] // 重複を排除 }
testInput
はstringSet
型の変数として宣言され、テスト対象の文字列を保持します。stringSet
は、文字列のスライスset
を内部に持ちます。add(s string)
: スライスに文字列s
を追加します。values() []string
:compact()
を呼び出してスライスをソートし、重複を排除した後に、そのスライスを返します。compact()
: 内部のスライスをソートし、隣接する重複要素を削除することで、ユニークな文字列のセットを作成します。
-
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」と出力し、テストが成功したことを示します。
-
main
関数内の条件分岐:if *test { testCollator(c) } else { // ... 既存のテーブル生成ロジック ... }
main
関数は、--test
フラグが設定されているかどうか (*test
の値) に基づいて、プログラムの実行パスを分岐させます。*test
がtrue
の場合、testCollator(c)
が呼び出され、照合キーの比較テストが実行されます。*test
がfalse
の場合、元の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
パッケージの概念を理解する上で重要です。)
- Go言語の公式ドキュメント (当時のバージョンに基づく): https://pkg.go.dev/exp/locale/collate (現在のGoのバージョンでは
- Unicode Collation Algorithm (UCA):
- Unicode Consortium: https://unicode.org/reports/tr10/
- Default Unicode Collation Element Table (DUCET):
- Unicode Consortium: https://unicode.org/Public/UCA/ (このコミットで参照されている
allkeys.txt
が含まれるディレクトリ)
- Unicode Consortium: https://unicode.org/Public/UCA/ (このコミットで参照されている
- Go言語の
flag
パッケージ:- Go言語の公式ドキュメント: https://pkg.go.dev/flag
- Go言語の
bytes
パッケージ:- Go言語の公式ドキュメント: https://pkg.go.dev/bytes
- Go言語の
sort
パッケージ:- Go言語の公式ドキュメント: https://pkg.go.dev/sort
参考にした情報源リンク
- Go言語の公式ドキュメント (pkg.go.dev)
- Unicode Consortium のウェブサイト (unicode.org)
- Go言語のソースコードリポジトリ (GitHub)
- 一般的なプログラミングにおける国際化 (i18n) とローカライゼーション (l10n) の概念に関する情報
- Go言語の
flag
、bytes
、sort
パッケージの利用例に関する情報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
にテスト機能を追加するための主要な変更を示しています。
-
import
文の追加:"bytes"
: バイトスライスを比較するためにbytes.Compare
関数を使用するために追加されました。"sort"
: 文字列のスライスをソートするためにsort.Strings
関数を使用するために追加されました。
-
test
フラグの定義:var test = flag.Bool("test", false, "test existing tables; can be used to compare web data with package data")
flag.Bool
を使用して、--test
という名前のブール型コマンドラインフラグを定義しています。デフォルト値はfalse
で、説明として「既存のテーブルをテストする; ウェブデータとパッケージデータを比較するために使用できる」と記述されています。 -
parseUCA
関数内の条件付きtestInput.add
:if *test { testInput.add(string(lhs)) }
parseUCA
関数は、DUCETデータから照合ルールを解析するループ内で実行されます。*test
(つまり--test
フラグがtrue
の場合) の条件が満たされると、解析された照合対象の文字列 (lhs
) がtestInput
というグローバルなstringSet
インスタンスに追加されます。これにより、DUCETデータに含まれるすべての照合対象文字列がテスト対象として収集されます。 -
testInput
とstringSet
の定義: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] // 重複を排除 }
testInput
はstringSet
型の変数として宣言され、テスト対象の文字列を保持します。stringSet
は、文字列のスライスset
を内部に持ちます。add(s string)
: スライスに文字列s
を追加します。values() []string
:compact()
を呼び出してスライスをソートし、重複を排除した後に、そのスライスを返します。compact()
: 内部のスライスをソートし、隣接する重複要素を削除することで、ユニークな文字列のセットを作成します。
-
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」と出力し、テストが成功したことを示します。
-
main
関数内の条件分岐:if *test { testCollator(c) } else { // ... 既存のテーブル生成ロジック ... }
main
関数は、--test
フラグが設定されているかどうか (*test
の値) に基づいて、プログラムの実行パスを分岐させます。*test
がtrue
の場合、testCollator(c)
が呼び出され、照合キーの比較テストが実行されます。*test
がfalse
の場合、元の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
パッケージの概念を理解する上で重要です。)
- Go言語の公式ドキュメント (当時のバージョンに基づく): https://pkg.go.dev/exp/locale/collate (現在のGoのバージョンでは
- Unicode Collation Algorithm (UCA):
- Unicode Consortium: https://unicode.org/reports/tr10/
- Default Unicode Collation Element Table (DUCET):
- Unicode Consortium: https://unicode.org/Public/UCA/ (このコミットで参照されている
allkeys.txt
が含まれるディレクトリ)
- Unicode Consortium: https://unicode.org/Public/UCA/ (このコミットで参照されている
- Go言語の
flag
パッケージ:- Go言語の公式ドキュメント: https://pkg.go.dev/flag
- Go言語の
bytes
パッケージ:- Go言語の公式ドキュメント: https://pkg.go.dev/bytes
- Go言語の
sort
パッケージ:- Go言語の公式ドキュメント: https://pkg.go.dev/sort
参考にした情報源リンク
- Go言語の公式ドキュメント (pkg.go.dev)
- Unicode Consortium のウェブサイト (unicode.org)
- Go言語のソースコードリポジトリ (GitHub)
- 一般的なプログラミングにおける国際化 (i18n) とローカライゼーション (l10n) の概念に関する情報
- Go言語の
flag
、bytes
、sort
パッケージの利用例に関する情報