[インデックス 1471] ファイルの概要
このコミットは、Go言語の標準ライブラリにUnicode関連の機能の初期実装を追加するものです。具体的には、unicodeパッケージのスタブ(骨格)が導入され、その中で特に文字が大文字であるか (IsUpper)、または文字であるか (IsLetter) を判定する機能が提供されます。これは、Go言語が多言語対応を強化し、Unicode文字の適切な処理を可能にするための第一歩となります。
コミット
commit 5ea8ac78a8c701c3808460ffea2c0bdae8f186cb
Author: Russ Cox <rsc@golang.org>
Date: Wed Jan 14 14:05:00 2009 -0800
add unicode library stub with just
IsUpper and IsLetter.
R=r
DELTA=746 (746 added, 0 deleted, 0 changed)
OCL=22743
CL=22750
---
src/lib/Makefile | 1 +
src/lib/unicode/Makefile | 55 ++++\n src/lib/unicode/letter.go | 569 +++++++++++++++++++++++++++++++++++++++++
src/lib/unicode/letter_test.go | 129 ++++++++++
4 files changed, 754 insertions(+)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5ea8ac78a8c701c3808460ffea2c0bdae8f186cb
元コミット内容
このコミットは、Go言語の標準ライブラリにunicodeパッケージの初期バージョンを追加します。この初期バージョンには、IsUpper(文字が大文字であるかを判定)とIsLetter(文字が文字であるかを判定)の2つの関数が含まれています。これらの関数は、Unicodeの文字プロパティに基づいて文字を分類するために使用されます。
変更されたファイルは以下の通りです。
src/lib/Makefile:unicodeパッケージをビルド対象に追加。src/lib/unicode/Makefile:unicodeパッケージ専用のMakefileを新規作成。src/lib/unicode/letter.go:IsUpperとIsLetter関数の実装、およびそれらが参照するUnicode文字範囲のデータ(UpperとLetter変数)を定義。src/lib/unicode/letter_test.go:IsUpperとIsLetter関数のテストコードを新規作成。
変更の背景
Go言語は、当初から国際化と多言語対応を重視していました。プログラミング言語が扱うテキストデータは、ASCII文字だけでなく、世界中の多様な言語の文字(漢字、キリル文字、アラビア文字など)を含むUnicode文字セットで表現されます。これらの文字を正しく処理するためには、文字のプロパティ(大文字、小文字、数字、記号、文字の方向など)を正確に識別する機能が不可欠です。
このコミットは、Go言語の標準ライブラリにUnicode文字の基本的なプロパティ判定機能を提供する最初のステップです。特に、IsUpperとIsLetterは、テキスト処理、文字列の正規化、識別子の検証など、多くのプログラミングタスクで頻繁に必要とされる基本的な機能です。この時点では「スタブ」と明記されているように、将来的にUnicodeの全プロパティを網羅する本格的なライブラリへと発展させるための基盤として導入されました。
前提知識の解説
Unicode
Unicodeは、世界中のあらゆる文字をコンピュータで扱うための文字コードの国際標準です。各文字には一意の「コードポイント」と呼ばれる数値が割り当てられています。例えば、AはU+0041、あはU+3042といった具合です。
文字プロパティ
Unicodeでは、各コードポイントに対して様々な「プロパティ」が定義されています。これには、文字のカテゴリ(例: 大文字、小文字、数字、記号)、スクリプト(例: ラテン文字、キリル文字、日本語)、双方向性プロパティなどが含まれます。これらのプロパティは、テキストの表示、ソート、検索、解析など、多岐にわたる処理で利用されます。
文字範囲 (Range)
Unicodeの文字プロパティは、連続するコードポイントの範囲(レンジ)として定義されることが多いです。例えば、ラテン文字の大文字AからZはU+0041からU+005Aまでの連続した範囲です。このコミットで導入されるRange構造体は、このような連続するコードポイントの範囲を効率的に表現するために使用されます。
lo: 範囲の開始コードポイント(low)hi: 範囲の終了コードポイント(high)stride: 範囲内のコードポイントが連続している場合(stride=1)や、特定の規則で飛び飛びになっている場合(stride>1)を示す値。例えば、stride=2であれば、lo,lo+2,lo+4, ... のように2つおきに文字が存在することを意味します。
Go言語のパッケージとビルドシステム
Go言語では、コードは「パッケージ」という単位で管理されます。各パッケージは通常、独自のディレクトリを持ち、その中にソースコードファイル(.go)やテストファイル(_test.go)が含まれます。Makefileは、Goの初期のビルドシステムで、パッケージのコンパイル、テスト、インストールなどの手順を定義するために使用されていました。
技術的詳細
このコミットの核となるのは、src/lib/unicode/letter.goで定義されているRange構造体と、Is、IsUpper、IsLetter関数です。
Range構造体
export type Range struct {
lo int;
hi int;
stride int;
}
Range構造体は、Unicodeコードポイントの連続した範囲を表現します。loは範囲の開始、hiは範囲の終了、strideは範囲内のコードポイントがどれだけの間隔で並んでいるかを示します。strideが1の場合、loからhiまでの全てのコードポイントが範囲に含まれます。strideが1より大きい場合、loから始まりstride間隔でhi以下のコードポイントが範囲に含まれます。
UpperとLetter変数
letter.goファイルには、UpperとLetterという2つのグローバル変数が定義されています。これらはそれぞれRange構造体のスライス(配列)であり、Unicodeの大文字と、文字(Letter)に分類されるコードポイントの範囲を保持しています。これらのデータは、Unicodeの公式データに基づいて手動で生成されたものですが、コメントには将来的に自動生成されるべきであるという意図が示されています。
Is関数
export func Is(ranges []Range, rune int) bool {
// common case: rune is ASCII or Latin-1
if rune < 0x100 {
for i := 0; i < len(ranges); i++ {
r := ranges[i];
if rune > r.hi {
continue;
}
if rune < r.lo {
return false;
}
return (rune - r.lo) % r.stride == 0;
}
return false;
}
// binary search over ranges
lo := 0;
hi := len(ranges);
for lo < hi {
m := lo + (hi - lo)/2;
r := ranges[m];
if r.lo <= rune && rune <= r.hi {
return (rune - r.lo) % r.stride == 0;
}
if rune < r.lo {
hi = m;
} else {
lo = m+1;
}
}
return false;
}
Is関数は、与えられたrune(Go言語におけるUnicodeコードポイントを表す型)が、指定されたranges(Rangeのスライス)のいずれかに含まれるかどうかを判定します。
- ASCII/Latin-1の高速パス:
runeが0x100(256)未満の場合、つまりASCIIまたはLatin-1の範囲内であれば、線形探索で高速に判定を試みます。これは、これらの範囲の文字が非常に頻繁に利用されるため、最適化されています。 - 二分探索: それ以外の
runeについては、rangesスライスに対して二分探索を行います。rangesはloの値でソートされているため、二分探索によって効率的に該当する範囲を見つけることができます。- 探索中の
Rangerに対して、runeがr.lo以上かつr.hi以下である場合、その範囲内にruneが含まれる可能性があります。 - 最終的な判定は
(rune - r.lo) % r.stride == 0で行われます。これは、runeが範囲の開始点r.loからstrideの倍数だけ離れているかどうかを確認することで、strideが1より大きい場合の飛び飛びの文字も正しく判定できるようにするためです。
- 探索中の
IsUpperとIsLetter関数
export func IsUpper(rune int) bool {
return Is(Upper, rune);
}
export func IsLetter(rune int) bool {
return Is(Letter, rune);
}
これらの関数は、Is関数をUpperまたはLetterのRangeスライスと共に呼び出すことで、それぞれ文字が大文字であるか、または文字であるかを判定します。これにより、ユーザーは直接Range構造体を扱うことなく、高レベルなAPIでUnicode文字のプロパティをチェックできます。
コアとなるコードの変更箇所
このコミットの主要な変更は、src/lib/unicode/letter.goファイルの新規追加です。
// src/lib/unicode/letter.go (抜粋)
package unicode
export type Range struct {
lo int;
hi int;
stride int;
}
export var Upper = []Range{
// ... 大文字のUnicode範囲データ ...
}
export var Letter = []Range {
// ... 文字(Letter)のUnicode範囲データ ...
}
export func Is(ranges []Range, rune int) bool {
// ... Is関数の実装 ...
}
export func IsUpper(rune int) bool {
return Is(Upper, rune);
}
export func IsLetter(rune int) bool {
return Is(Letter, rune);
}
また、src/lib/Makefileにunicodeディレクトリが追加され、src/lib/unicode/Makefileとsrc/lib/unicode/letter_test.goが新規作成されています。
コアとなるコードの解説
letter.goファイルは、Go言語におけるUnicode文字プロパティ判定の基礎を築いています。
- データ駆動型アプローチ:
UpperとLetterというRangeのスライスにUnicodeの文字範囲データを格納することで、判定ロジックとデータを分離しています。これにより、将来的にUnicodeのバージョンアップに伴ってデータが変更されても、Is関数のロジック自体を変更する必要がなくなります。 - 効率的な検索:
Is関数内の二分探索は、大量のUnicode文字範囲データの中から特定の文字が属する範囲を効率的に見つけるための重要な最適化です。これにより、IsUpperやIsLetterの呼び出しが高速に実行されます。 - 拡張性: この設計は、将来的に他のUnicodeプロパティ(例:
IsDigit,IsSpace,IsPunctなど)を追加する際に、新しいRangeスライスとそれに対応するシンプルなラッパー関数を追加するだけで済むように考慮されています。
letter_test.goでは、IsUpperとIsLetter関数が正しく機能するかを確認するためのテストケースが記述されています。upper, notupper, letter, notletterというスライスに、それぞれ期待される結果(大文字であるべき文字、大文字ではないべき文字、文字であるべき文字、文字ではないべき文字)のコードポイントが格納されており、これらのデータを使って各関数が正しいブール値を返すか検証しています。
関連リンク
- Go言語の公式ドキュメント(現在の
unicodeパッケージ): https://pkg.go.dev/unicode - Unicode Consortium: https://home.unicode.org/
参考にした情報源リンク
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- Unicode Standard: https://www.unicode.org/versions/latest/
- Go言語の初期のコミット履歴(GitHub)
- Go言語の
unicodeパッケージの進化に関する情報(Goブログや関連ドキュメント)