[インデックス 1286] ファイルの概要
このコミットは、Go言語の標準ライブラリにおけるUTF-8文字の長さを数える機能の再編成と改善を目的としています。具体的には、strings
パッケージにあったutflen
関数を廃止し、その機能をより適切で汎用的なutf8
パッケージ内のRuneCount
およびRuneCountInString
関数に移行しています。これにより、UTF-8処理に関する責任がutf8
パッケージに集約され、ライブラリの設計がより明確になります。
コミット
Author: Russ Cox rsc@golang.org Date: Thu Dec 4 21:00:34 2008 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d1cbaf22524113eb49347c1194084c572e5a003
元コミット内容
strings.utflen -> utf8.RuneCount, RuneCountInString
R=r
DELTA=94 (52 added, 33 deleted, 9 changed)
OCL=20547
CL=20552
変更の背景
この変更の背景には、Go言語の標準ライブラリにおけるパッケージ設計の原則と、UTF-8文字エンコーディングの適切な取り扱いがあります。
初期のGo言語の設計段階では、文字列操作に関する機能がstrings
パッケージに集約されていました。しかし、UTF-8は可変長エンコーディングであり、1つの文字(Goでは「rune」と呼ぶ)が1バイトから4バイトの範囲で表現されます。そのため、文字列の「長さ」を数える際に、バイト数と文字数(rune数)を区別することが重要になります。
strings
パッケージにutflen
という関数が存在していましたが、これはUTF-8の文字数を数える機能を提供していました。しかし、UTF-8エンコーディングの低レベルな処理は、より専門的なutf8
パッケージの役割であるべきという設計思想がありました。strings
パッケージは、より高レベルな文字列操作(分割、結合、検索など)に焦点を当てるべきであり、UTF-8のバイト列から文字をデコードしたり、文字数を数えたりするようなプリミティブな操作はutf8
パッケージが担当するべきです。
このコミットは、この設計原則に沿って、utflen
の機能をutf8
パッケージに移管し、より明確なAPIとしてRuneCount
とRuneCountInString
を導入することで、ライブラリの責務を明確化し、将来的な拡張性や保守性を向上させることを目的としています。また、strings
パッケージ内のexplode
やcount
といった関数がutflen
に依存していたため、これらの関数も新しいutf8
パッケージの関数を使用するように更新されています。
前提知識の解説
UTF-8とRune
- UTF-8: Unicode Transformation Format - 8-bitの略で、Unicode文字を可変長のバイト列で表現するエンコーディング方式です。ASCII文字は1バイトで表現され、それ以外の文字は2バイトから4バイトで表現されます。これにより、世界中の多様な言語の文字を効率的に扱うことができます。
- Rune (Go言語における): Go言語では、
rune
型はUnicodeコードポイントを表すために使用される組み込みの整数型(int32
のエイリアス)です。Goの文字列はUTF-8バイト列として内部的に表現されますが、文字列をイテレートしたり、文字数を数えたりする際には、バイト数ではなくrune数(つまり、人間が認識する文字数)を扱うことが一般的です。
strings
パッケージとutf8
パッケージ
strings
パッケージ: Go言語の標準ライブラリの一部で、文字列の操作(検索、置換、分割、結合など)に関する高レベルな機能を提供します。このパッケージは、UTF-8の内部表現を意識することなく、文字列を抽象的に扱うためのユーティリティを提供します。utf8
パッケージ: Go言語の標準ライブラリの一部で、UTF-8エンコーディングの低レベルな処理に関する機能を提供します。バイト列からruneをデコードしたり、runeをバイト列にエンコードしたり、UTF-8バイト列の有効性をチェックしたりする機能が含まれます。このパッケージは、UTF-8のバイト表現とUnicodeコードポイント(rune)の間の変換を専門とします。
文字列の長さの概念
UTF-8では、文字列の「長さ」には複数の解釈があります。
- バイト数: 文字列を構成するバイトの総数。これは
len(s)
で取得できます。 - Rune数(文字数): 文字列に含まれるUnicodeコードポイント(rune)の総数。これは人間が認識する文字数に近いです。例えば、日本語の「あ」は3バイトですが、1runeです。
このコミットは、特に2番目の「Rune数」を正確かつ効率的に数えるための機能の再配置と改善に焦点を当てています。
技術的詳細
このコミットの主要な技術的変更点は、UTF-8文字カウント機能のstrings
パッケージからutf8
パッケージへの移行と、その実装の詳細にあります。
utflen
の廃止とRuneCount
/RuneCountInString
の導入
utflen
の廃止:src/lib/strings.go
からutflen
関数が削除されました。この関数は、UTF-8バイト列を走査し、マルチバイト文字の開始バイト(0xC0
と0x80
のビットマスクで判定)ではないバイトを数えることで、rune数を概算していました。しかし、この実装は完全なUTF-8のデコードロジックを含んでおらず、utf8
パッケージのより堅牢なデコード機能を利用すべきでした。RuneCount
とRuneCountInString
の導入:src/lib/utf8.go
に以下の2つの新しい関数が追加されました。export func RuneCount(p *[]byte) int
: バイトスライスp
に含まれるUTF-8 runeの数を返します。これはバイトスライスを直接操作する場合に使用されます。export func RuneCountInString(s string, i int, l int) int
: 文字列s
の指定された範囲(i
からi+l
まで)に含まれるUTF-8 runeの数を返します。これは文字列の一部に対してrune数を数える場合に便利です。
これらの新しい関数は、utf8.DecodeRune
やutf8.DecodeRuneInStringInternal
といった既存の堅牢なUTF-8デコード関数を利用して、正確にrune数をカウントします。これにより、utflen
が持っていた潜在的な不正確さや、strings
パッケージがUTF-8の低レベルな詳細に依存するという設計上の問題が解消されます。
DecodeRuneInStringInternal
の変更
src/lib/utf8.go
のDecodeRuneInStringInternal
関数のシグネチャが変更されました。
変更前: func DecodeRuneInStringInternal(s string, i int) (rune, size int, short bool)
変更後: func DecodeRuneInStringInternal(s string, i int, n int) (rune, size int, short bool)
新しいn
パラメータは、デコード対象の文字列の残りのバイト数を示します。これにより、関数は文字列の末尾に到達したかどうかをより正確に判断できるようになり、部分的なUTF-8シーケンス(不完全な文字)の処理が改善されます。この変更は、RuneCountInString
やFullRuneInString
といった関数が、文字列の特定の範囲内でデコードを行う際に、より正確な情報を提供できるようにするために行われました。
strings
パッケージの依存関係の更新
strings
パッケージ内のexplode
関数とcount
関数は、以前utflen
に依存していました。このコミットでは、これらの関数が新しく導入されたutf8.RuneCountInString
を使用するように更新されました。
explode
関数: 文字列を個々のUTF-8 runeの文字列スライスに分割する関数です。以前はutflen(s)
でスライスの初期サイズを決定していましたが、utf8.RuneCountInString(s, 0, len(s))
を使用するように変更されました。また、個々のruneを抽出するロジックも、utf8.DecodeRuneInString
を使用してより正確かつ簡潔になりました。count
関数: 文字列中に特定のセパレータが何回出現するかを数える関数です。セパレータが空文字列の場合、以前はutflen(s)+1
を返していましたが、これもutf8.RuneCountInString(s, 0, len(s))+1
に変更されました。これは、空文字列をセパレータとした場合、runeの間にセパレータが存在すると見なすため、rune数に1を加えるというロジックです。
テストの更新
src/lib/strings_test.go
からTestUtflen
が削除されました。これはutflen
関数が廃止されたためです。src/lib/utf8_test.go
にTestRuneCount
が追加されました。このテストは、新しく導入されたRuneCount
とRuneCountInString
関数の正確性を検証します。様々なUTF-8文字列(ASCII、マルチバイト文字、不正なUTF-8シーケンスを含むもの)に対して、期待されるrune数が返されることを確認しています。
Makefileの変更
src/lib/Makefile
にstrings.6: utf8.install
という行が追加されました。これは、strings
パッケージがutf8
パッケージに依存するようになったことをビルドシステムに伝えるためのものです。これにより、strings
パッケージをビルドする前にutf8
パッケージが適切にインストールされることが保証されます。
コアとなるコードの変更箇所
src/lib/Makefile
--- a/src/lib/Makefile
+++ b/src/lib/Makefile
@@ -88,6 +88,7 @@ bignum.6: fmt.dirinstall
bufio.6: io.dirinstall os.dirinstall
flag.6: fmt.dirinstall
testing.6: flag.install fmt.dirinstall
+strings.6: utf8.install
src/lib/strings.go
--- a/src/lib/strings.go
+++ b/src/lib/strings.go
@@ -4,30 +4,17 @@
package strings
-// Count UTF-8 sequences in s.
-// Assumes s is well-formed.
-export func utflen(s string) int {
- n := 0;
- for i := 0; i < len(s); i++ {
- if s[i]&0xC0 != 0x80 {
- n++
- }
- }
- return n
-}
+import "utf8"
// Split string into array of UTF-8 sequences (still strings)
export func explode(s string) *[]string {
- a := new([]string, utflen(s));
- j := 0;
+ a := new([]string, utf8.RuneCountInString(s, 0, len(s)));
+ j := 0;
+ var size, rune int;
for i := 0; i < len(a); i++ {
- ej := j;
- ej++;
- for ej < len(s) && (s[ej]&0xC0) == 0x80 {
- ej++
- }
- a[i] = s[j:ej];
- j = ej
+ rune, size = utf8.DecodeRuneInString(s, j);
+ a[i] = string(rune);
+ j += size;
}
return a
}
@@ -35,7 +22,7 @@ export func explode(s string) *[]string {
// Count non-overlapping instances of sep in s.
export func count(s, sep string) int {
if sep == "" {
- return utflen(s)+1
+ return utf8.RuneCountInString(s, 0, len(s))+1
}
c := sep[0];
n := 0;
src/lib/strings_test.go
--- a/src/lib/strings_test.go
+++ b/src/lib/strings_test.go
@@ -79,21 +79,3 @@ export func TestSplit(t *testing.T) {
}
}
-// TODO: utflen shouldn't even be in strings.
-type UtflenTest struct {
- in string;
- out int;
-}
-var utflentests = []UtflenTest {
- UtflenTest{ abcd, 4 },
- UtflenTest{ faces, 3 },
- UtflenTest{ commas, 7 },
-}
-export func TestUtflen(t *testing.T) {
- for i := 0; i < len(utflentests); i++ {
- tt := utflentests[i];
- if out := strings.utflen(tt.in); out != tt.out {
- t.Errorf("utflen(%q) = %d, want %d", tt.in, out, tt.out);
- }
- }
-}
src/lib/utf8.go
--- a/src/lib/utf8.go
+++ b/src/lib/utf8.go
@@ -107,8 +107,7 @@ func DecodeRuneInternal(p *[]byte) (rune, size int, short bool) {
return RuneError, 1, false
}
-func DecodeRuneInStringInternal(s string, i int) (rune, size int, short bool) {
- n := len(s) - i;
+func DecodeRuneInStringInternal(s string, i int, n int) (rune, size int, short bool) {
if n < 1 {
return RuneError, 0, true;
}
@@ -188,7 +187,7 @@ export func FullRune(p *[]byte) bool {
}
export func FullRuneInString(s string, i int) bool {
- rune, size, short := DecodeRuneInStringInternal(s, i);
+ rune, size, short := DecodeRuneInStringInternal(s, i, len(s) - i);
return !short
}
@@ -200,7 +199,7 @@ export func DecodeRune(p *[]byte) (rune, size int) {
export func DecodeRuneInString(s string, i int) (rune, size int) {
var short bool;
- rune, size, short = DecodeRuneInStringInternal(s, i);
+ rune, size, short = DecodeRuneInStringInternal(s, i, len(s) - i);
return;
}
@@ -248,3 +247,31 @@ export func EncodeRune(rune int, p *[]byte) int {
return 4;
}\n
+export func RuneCount(p *[]byte) int {
+ i := 0;
+ var n int;
+ for n = 0; i < len(p); n++ {
+ if p[i] < RuneSelf {
+ i++;
+ } else {
+ rune, size := DecodeRune(p[i:len(p)]);
+ i += size;
+ }
+ }
+ return n;
+}
+
+export func RuneCountInString(s string, i int, l int) int {
+ ei := i + l;
+ n := 0;
+ for n = 0; i < ei; n++ {
+ if s[i] < RuneSelf {
+ i++;
+ } else {
+ rune, size, short := DecodeRuneInStringInternal(s, i, ei - i);
+ i += size;
+ }
+ }
+ return n;
+}
src/lib/utf8_test.go
--- a/src/lib/utf8_test.go
+++ b/src/lib/utf8_test.go
@@ -156,3 +156,25 @@ export func TestDecodeRune(t *testing.T) {
}\n
}\n
+\n
+type RuneCountTest struct {
+\tin string;
+\tout int;
+}\n
+var runecounttests = []RuneCountTest {
+\tRuneCountTest{ "abcd", 4 },
+\tRuneCountTest{ "☺☻☹", 3 },
+\tRuneCountTest{ "1,2,3,4", 7 },
+\tRuneCountTest{ "\\xe2\\x00", 2 },
+}\n
+export func TestRuneCount(t *testing.T) {
+\tfor i := 0; i < len(runecounttests); i++ {
+\t\ttt := runecounttests[i];
+\t\tif out := utf8.RuneCountInString(tt.in, 0, len(tt.in)); out != tt.out {
+\t\t\tt.Errorf("RuneCountInString(%q) = %d, want %d", tt.in, out, tt.out);\n
+\t\t}\n
+\t\tif out := utf8.RuneCount(Bytes(tt.in)); out != tt.out {
+\t\t\tt.Errorf("RuneCount(%q) = %d, want %d", tt.in, out, tt.out);\n
+\t\t}\n
+\t}\n
+}\n
コアとなるコードの解説
src/lib/Makefile
の変更
strings.6: utf8.install
の追加は、ビルドシステムに対してstrings
パッケージがutf8
パッケージに依存していることを明示しています。これは、strings.go
がimport "utf8"
を追加したため、strings
パッケージをコンパイルする前にutf8
パッケージが利用可能である必要があるためです。
src/lib/strings.go
の変更
utflen
関数の削除:strings
パッケージからutflen
関数が完全に削除されました。これにより、strings
パッケージはUTF-8の低レベルな文字カウントロジックから解放され、より高レベルな文字列操作に特化するようになりました。import "utf8"
の追加:strings
パッケージがutf8
パッケージの機能を利用するために、import "utf8"
が追加されました。explode
関数の変更:a := new([]string, utf8.RuneCountInString(s, 0, len(s)));
以前はutflen(s)
でスライスのサイズを決定していましたが、より正確で標準的なutf8.RuneCountInString
を使用するように変更されました。これにより、文字列に含まれるruneの総数に基づいてスライスが適切に初期化されます。rune, size = utf8.DecodeRuneInString(s, j); a[i] = string(rune); j += size;
以前のexplode
関数は、バイト列を直接操作してUTF-8シーケンスの境界を検出していました。新しい実装では、utf8.DecodeRuneInString
という既存の堅牢な関数を利用して、文字列から次のruneとそのバイトサイズをデコードしています。これにより、より正確かつ安全にUTF-8文字列をruneごとに分割できるようになりました。string(rune)
は、デコードされたrune(Unicodeコードポイント)を単一の文字からなる文字列に変換します。
count
関数の変更:return utf8.RuneCountInString(s, 0, len(s))+1
セパレータが空文字列の場合の処理が、utflen(s)+1
からutf8.RuneCountInString(s, 0, len(s))+1
に変更されました。これは、文字列中のruneの間にセパレータが存在すると見なすというロジックを、新しいutf8
パッケージの関数に適合させたものです。
src/lib/strings_test.go
の変更
TestUtflen
の削除:utflen
関数が削除されたため、そのテスト関数も不要となり削除されました。
src/lib/utf8.go
の変更
DecodeRuneInStringInternal
のシグネチャ変更:func DecodeRuneInStringInternal(s string, i int, n int) (rune, size int, short bool)
この関数は、文字列s
のインデックスi
から始まるn
バイトの範囲でUTF-8 runeをデコードします。n
パラメータが追加されたことで、デコード対象の範囲を明示的に指定できるようになり、部分的な文字列に対するデコードの正確性が向上しました。FullRuneInString
とDecodeRuneInString
の更新: これらの関数は、内部的にDecodeRuneInStringInternal
を呼び出す際に、len(s) - i
をn
パラメータとして渡すように変更されました。これにより、文字列の残りのバイト数を正確にDecodeRuneInStringInternal
に伝えることができます。RuneCount
関数の追加:
この関数は、バイトスライスexport func RuneCount(p *[]byte) int { i := 0; var n int; for n = 0; i < len(p); n++ { if p[i] < RuneSelf { // ASCII文字の場合 i++; } else { // マルチバイト文字の場合 rune, size := DecodeRune(p[i:len(p)]); i += size; } } return n; }
p
を走査し、各runeのバイトサイズを考慮しながらruneの数をカウントします。RuneSelf
はASCII文字の最大値(128)を表す定数で、これより小さいバイト値は1バイトのASCII文字であることを示します。それ以外の場合はDecodeRune
を使用してruneのバイトサイズを正確に取得し、インデックスを進めます。RuneCountInString
関数の追加:
この関数は、文字列export func RuneCountInString(s string, i int, l int) int { ei := i + l; // 終了インデックス n := 0; for n = 0; i < ei; n++ { if s[i] < RuneSelf { // ASCII文字の場合 i++; } else { // マルチバイト文字の場合 rune, size, short := DecodeRuneInStringInternal(s, i, ei - i); i += size; } } return n; }
s
の指定された範囲(i
からei
まで)でruneの数をカウントします。基本的なロジックはRuneCount
と同様ですが、文字列を対象とし、DecodeRuneInStringInternal
を利用してデコードを行います。ei - i
は、現在の位置から範囲の終わりまでの残りのバイト数を示します。
src/lib/utf8_test.go
の変更
TestRuneCount
の追加: このテスト関数は、RuneCount
とRuneCountInString
の正確性を検証するために追加されました。様々なテストケース(ASCII文字列、マルチバイト文字を含む文字列、不正なUTF-8シーケンスを含む文字列)が用意されており、それぞれの入力に対して期待されるrune数が返されることを確認しています。これにより、新しい関数の信頼性が保証されます。
関連リンク
- Go言語の
strings
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/strings - Go言語の
unicode/utf8
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/unicode/utf8 - Go言語における文字列、バイト、runeの概念に関する公式ブログ記事 (例: "Strings, bytes, runes and characters in Go"): https://go.dev/blog/strings (これはコミット当時のものではありませんが、概念理解に役立ちます)
参考にした情報源リンク
- Go言語の公式ドキュメント (特に
strings
とunicode/utf8
パッケージ) - Go言語のソースコード (コミット履歴と関連ファイル)
- UTF-8エンコーディングに関する一般的な情報源 (例: Wikipedia)
- Go言語における文字列処理に関する技術ブログや解説記事# [インデックス 1286] ファイルの概要
このコミットは、Go言語の標準ライブラリにおけるUTF-8文字の長さを数える機能の再編成と改善を目的としています。具体的には、strings
パッケージにあったutflen
関数を廃止し、その機能をより適切で汎用的なutf8
パッケージ内のRuneCount
およびRuneCountInString
関数に移行しています。これにより、UTF-8処理に関する責任がutf8
パッケージに集約され、ライブラリの設計がより明確になります。
コミット
Author: Russ Cox rsc@golang.org Date: Thu Dec 4 21:00:34 2008 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d1cbaf22524113eb49347c1194084c572e5a003
元コミット内容
strings.utflen -> utf8.RuneCount, RuneCountInString
R=r
DELTA=94 (52 added, 33 deleted, 9 changed)
OCL=20547
CL=20552
変更の背景
この変更の背景には、Go言語の標準ライブラリにおけるパッケージ設計の原則と、UTF-8文字エンコーディングの適切な取り扱いがあります。
初期のGo言語の設計段階では、文字列操作に関する機能がstrings
パッケージに集約されていました。しかし、UTF-8は可変長エンコーディングであり、1つの文字(Goでは「rune」と呼ぶ)が1バイトから4バイトの範囲で表現されます。そのため、文字列の「長さ」を数える際に、バイト数と文字数(rune数)を区別することが重要になります。
strings
パッケージにutflen
という関数が存在していましたが、これはUTF-8の文字数を数える機能を提供していました。しかし、UTF-8エンコーディングの低レベルな処理は、より専門的なutf8
パッケージの役割であるべきという設計思想がありました。strings
パッケージは、より高レベルな文字列操作(分割、結合、検索など)に焦点を当てるべきであり、UTF-8のバイト列から文字をデコードしたり、文字数を数えたりするようなプリミティブな操作はutf8
パッケージが担当するべきです。
このコミットは、この設計原則に沿って、utflen
の機能をutf8
パッケージに移管し、より明確なAPIとしてRuneCount
とRuneCountInString
を導入することで、ライブラリの責務を明確化し、将来的な拡張性や保守性を向上させることを目的としています。また、strings
パッケージ内のexplode
やcount
といった関数がutflen
に依存していたため、これらの関数も新しいutf8
パッケージの関数を使用するように更新されています。
前提知識の解説
UTF-8とRune
- UTF-8: Unicode Transformation Format - 8-bitの略で、Unicode文字を可変長のバイト列で表現するエンコーディング方式です。ASCII文字は1バイトで表現され、それ以外の文字は2バイトから4バイトで表現されます。これにより、世界中の多様な言語の文字を効率的に扱うことができます。
- Rune (Go言語における): Go言語では、
rune
型はUnicodeコードポイントを表すために使用される組み込みの整数型(int32
のエイリアス)です。Goの文字列はUTF-8バイト列として内部的に表現されますが、文字列をイテレートしたり、文字数を数えたりする際には、バイト数ではなくrune数(つまり、人間が認識する文字数)を扱うことが一般的です。
strings
パッケージとutf8
パッケージ
strings
パッケージ: Go言語の標準ライブラリの一部で、文字列の操作(検索、置換、分割、結合など)に関する高レベルな機能を提供します。このパッケージは、UTF-8の内部表現を意識することなく、文字列を抽象的に扱うためのユーティリティを提供します。utf8
パッケージ: Go言語の標準ライブラリの一部で、UTF-8エンコーディングの低レベルな処理に関する機能を提供します。バイト列からruneをデコードしたり、runeをバイト列にエンコードしたり、UTF-8バイト列の有効性をチェックしたりする機能が含まれます。このパッケージは、UTF-8のバイト表現とUnicodeコードポイント(rune)の間の変換を専門とします。
文字列の長さの概念
UTF-8では、文字列の「長さ」には複数の解釈があります。
- バイト数: 文字列を構成するバイトの総数。これは
len(s)
で取得できます。 - Rune数(文字数): 文字列に含まれるUnicodeコードポイント(rune)の総数。これは人間が認識する文字数に近いです。例えば、日本語の「あ」は3バイトですが、1runeです。
このコミットは、特に2番目の「Rune数」を正確かつ効率的に数えるための機能の再配置と改善に焦点を当てています。
技術的詳細
このコミットの主要な技術的変更点は、UTF-8文字カウント機能のstrings
パッケージからutf8
パッケージへの移行と、その実装の詳細にあります。
utflen
の廃止とRuneCount
/RuneCountInString
の導入
utflen
の廃止:src/lib/strings.go
からutflen
関数が削除されました。この関数は、UTF-8バイト列を走査し、マルチバイト文字の開始バイト(0xC0
と0x80
のビットマスクで判定)ではないバイトを数えることで、rune数を概算していました。しかし、この実装は完全なUTF-8のデコードロジックを含んでおらず、utf8
パッケージのより堅牢なデコード機能を利用すべきでした。RuneCount
とRuneCountInString
の導入:src/lib/utf8.go
に以下の2つの新しい関数が追加されました。export func RuneCount(p *[]byte) int
: バイトスライスp
に含まれるUTF-8 runeの数を返します。これはバイトスライスを直接操作する場合に使用されます。export func RuneCountInString(s string, i int, l int) int
: 文字列s
の指定された範囲(i
からi+l
まで)に含まれるUTF-8 runeの数を返します。これは文字列の一部に対してrune数を数える場合に便利です。
これらの新しい関数は、utf8.DecodeRune
やutf8.DecodeRuneInStringInternal
といった既存の堅牢なUTF-8デコード関数を利用して、正確にrune数をカウントします。これにより、utflen
が持っていた潜在的な不正確さや、strings
パッケージがUTF-8の低レベルな詳細に依存するという設計上の問題が解消されます。
DecodeRuneInStringInternal
の変更
src/lib/utf8.go
のDecodeRuneInStringInternal
関数のシグネチャが変更されました。
変更前: func DecodeRuneInStringInternal(s string, i int) (rune, size int, short bool)
変更後: func DecodeRuneInStringInternal(s string, i int, n int) (rune, size int, short bool)
新しいn
パラメータは、デコード対象の文字列の残りのバイト数を示します。これにより、関数は文字列の末尾に到達したかどうかをより正確に判断できるようになり、部分的なUTF-8シーケンス(不完全な文字)の処理が改善されます。この変更は、RuneCountInString
やFullRuneInString
といった関数が、文字列の特定の範囲内でデコードを行う際に、より正確な情報を提供できるようにするために行われました。
strings
パッケージの依存関係の更新
strings
パッケージ内のexplode
関数とcount
関数は、以前utflen
に依存していました。このコミットでは、これらの関数が新しく導入されたutf8.RuneCountInString
を使用するように更新されました。
explode
関数: 文字列を個々のUTF-8 runeの文字列スライスに分割する関数です。以前はutflen(s)
でスライスの初期サイズを決定していましたが、utf8.RuneCountInString(s, 0, len(s))
を使用するように変更されました。また、個々のruneを抽出するロジックも、utf8.DecodeRuneInString
を使用してより正確かつ簡潔になりました。count
関数: 文字列中に特定のセパレータが何回出現するかを数える関数です。セパレータが空文字列の場合、以前はutflen(s)+1
を返していましたが、これもutf8.RuneCountInString(s, 0, len(s))+1
に変更されました。これは、空文字列をセパレータとした場合、runeの間にセパレータが存在すると見なすため、rune数に1を加えるというロジックです。
テストの更新
src/lib/strings_test.go
からTestUtflen
が削除されました。これはutflen
関数が廃止されたためです。src/lib/utf8_test.go
にTestRuneCount
が追加されました。このテストは、新しく導入されたRuneCount
とRuneCountInString
関数の正確性を検証します。様々なUTF-8文字列(ASCII、マルチバイト文字、不正なUTF-8シーケンスを含むもの)に対して、期待されるrune数が返されることを確認しています。
Makefileの変更
src/lib/Makefile
にstrings.6: utf8.install
という行が追加されました。これは、strings
パッケージがutf8
パッケージに依存するようになったことをビルドシステムに伝えるためのものです。これにより、strings
パッケージをビルドする前にutf8
パッケージが適切にインストールされることが保証されます。
コアとなるコードの変更箇所
src/lib/Makefile
--- a/src/lib/Makefile
+++ b/src/lib/Makefile
@@ -88,6 +88,7 @@ bignum.6: fmt.dirinstall
bufio.6: io.dirinstall os.dirinstall
flag.6: fmt.dirinstall
testing.6: flag.install fmt.dirinstall
+strings.6: utf8.install
src/lib/strings.go
--- a/src/lib/strings.go
+++ b/src/lib/strings.go
@@ -4,30 +4,17 @@
package strings
-// Count UTF-8 sequences in s.
-// Assumes s is well-formed.
-export func utflen(s string) int {
- n := 0;
- for i := 0; i < len(s); i++ {
- if s[i]&0xC0 != 0x80 {
- n++
- }
- }
- return n
-}
+import "utf8"
// Split string into array of UTF-8 sequences (still strings)
export func explode(s string) *[]string {
- a := new([]string, utflen(s));
- j := 0;
+ a := new([]string, utf8.RuneCountInString(s, 0, len(s)));
+ j := 0;
+ var size, rune int;
for i := 0; i < len(a); i++ {
- ej := j;
- ej++;
- for ej < len(s) && (s[ej]&0xC0) == 0x80 {
- ej++
- }
- a[i] = s[j:ej];
- j = ej
+ rune, size = utf8.DecodeRuneInString(s, j);
+ a[i] = string(rune);
+ j += size;
}
return a
}
@@ -35,7 +22,7 @@ export func explode(s string) *[]string {
// Count non-overlapping instances of sep in s.
export func count(s, sep string) int {
if sep == "" {
- return utflen(s)+1
+ return utf8.RuneCountInString(s, 0, len(s))+1
}
c := sep[0];
n := 0;
src/lib/strings_test.go
--- a/src/lib/strings_test.go
+++ b/src/lib/strings_test.go
@@ -79,21 +79,3 @@ export func TestSplit(t *testing.T) {
}
}
-// TODO: utflen shouldn't even be in strings.
-type UtflenTest struct {
- in string;
- out int;
-}
-var utflentests = []UtflenTest {
- UtflenTest{ abcd, 4 },
- UtflenTest{ faces, 3 },
- UtflenTest{ commas, 7 },
-}
-export func TestUtflen(t *testing.T) {
- for i := 0; i < len(utflentests); i++ {
- tt := utflentests[i];
- if out := strings.utflen(tt.in); out != tt.out {
- t.Errorf("utflen(%q) = %d, want %d", tt.in, out, tt.out);
- }
- }
-}
src/lib/utf8.go
--- a/src/lib/utf8.go
+++ b/src/lib/utf8.go
@@ -107,8 +107,7 @@ func DecodeRuneInternal(p *[]byte) (rune, size int, short bool) {
return RuneError, 1, false
}
-func DecodeRuneInStringInternal(s string, i int) (rune, size int, short bool) {
- n := len(s) - i;
+func DecodeRuneInStringInternal(s string, i int, n int) (rune, size int, short bool) {
if n < 1 {
return RuneError, 0, true;
}
@@ -188,7 +187,7 @@ export func FullRune(p *[]byte) bool {
}
export func FullRuneInString(s string, i int) bool {
- rune, size, short := DecodeRuneInStringInternal(s, i);
+ rune, size, short := DecodeRuneInStringInternal(s, i, len(s) - i);
return !short
}
@@ -200,7 +199,7 @@ export func DecodeRune(p *[]byte) (rune, size int) {
export func DecodeRuneInString(s string, i int) (rune, size int) {
var short bool;
- rune, size, short = DecodeRuneInStringInternal(s, i);
+ rune, size, short = DecodeRuneInStringInternal(s, i, len(s) - i);
return;
}
@@ -248,3 +247,31 @@ export func EncodeRune(rune int, p *[]byte) int {
return 4;
}\n
+export func RuneCount(p *[]byte) int {
+ i := 0;
+ var n int;
+ for n = 0; i < len(p); n++ {
+ if p[i] < RuneSelf { // ASCII文字の場合
+ i++;
+ } else { // マルチバイト文字の場合
+ rune, size := DecodeRune(p[i:len(p)]);
+ i += size;
+ }
+ }
+ return n;
+}
+
+export func RuneCountInString(s string, i int, l int) int {
+ ei := i + l; // 終了インデックス
+ n := 0;
+ for n = 0; i < ei; n++ {
+ if s[i] < RuneSelf { // ASCII文字の場合
+ i++;
+ } else { // マルチバイト文字の場合
+ rune, size, short := DecodeRuneInStringInternal(s, i, ei - i);
+ i += size;
+ }
+ }
+ return n;
+}
src/lib/utf8_test.go
--- a/src/lib/utf8_test.go
+++ b/src/lib/utf8_test.go
@@ -156,3 +156,25 @@ export func TestDecodeRune(t *testing.T) {
}\n
}\n
+\n
+type RuneCountTest struct {
+\tin string;
+\tout int;
+}\n
+var runecounttests = []RuneCountTest {
+\tRuneCountTest{ "abcd", 4 },
+\tRuneCountTest{ "☺☻☹", 3 },
+\tRuneCountTest{ "1,2,3,4", 7 },
+\tRuneCountTest{ "\\xe2\\x00", 2 },
+}\n
+export func TestRuneCount(t *testing.T) {
+\tfor i := 0; i < len(runecounttests); i++ {
+\t\ttt := runecounttests[i];
+\t\tif out := utf8.RuneCountInString(tt.in, 0, len(tt.in)); out != tt.out {
+\t\t\tt.Errorf("RuneCountInString(%q) = %d, want %d", tt.in, out, tt.out);\n
+\t\t}\n
+\t\tif out := utf8.RuneCount(Bytes(tt.in)); out != tt.out {
+\t\t\tt.Errorf("RuneCount(%q) = %d, want %d", tt.in, out, tt.out);\n
+\t\t}\n
+\t}\n
+}\n
コアとなるコードの解説
src/lib/Makefile
の変更
strings.6: utf8.install
の追加は、ビルドシステムに対してstrings
パッケージがutf8
パッケージに依存していることを明示しています。これは、strings.go
がimport "utf8"
を追加したため、strings
パッケージをコンパイルする前にutf8
パッケージが利用可能である必要があるためです。
src/lib/strings.go
の変更
utflen
関数の削除:strings
パッケージからutflen
関数が完全に削除されました。これにより、strings
パッケージはUTF-8の低レベルな文字カウントロジックから解放され、より高レベルな文字列操作に特化するようになりました。import "utf8"
の追加:strings
パッケージがutf8
パッケージの機能を利用するために、import "utf8"
が追加されました。explode
関数の変更:a := new([]string, utf8.RuneCountInString(s, 0, len(s)));
以前はutflen(s)
でスライスのサイズを決定していましたが、より正確で標準的なutf8.RuneCountInString
を使用するように変更されました。これにより、文字列に含まれるruneの総数に基づいてスライスが適切に初期化されます。rune, size = utf8.DecodeRuneInString(s, j); a[i] = string(rune); j += size;
以前のexplode
関数は、バイト列を直接操作してUTF-8シーケンスの境界を検出していました。新しい実装では、utf8.DecodeRuneInString
という既存の堅牢な関数を利用して、文字列から次のruneとそのバイトサイズをデコードしています。これにより、より正確かつ安全にUTF-8文字列をruneごとに分割できるようになりました。string(rune)
は、デコードされたrune(Unicodeコードポイント)を単一の文字からなる文字列に変換します。
count
関数の変更:return utf8.RuneCountInString(s, 0, len(s))+1
セパレータが空文字列の場合の処理が、utflen(s)+1
からutf8.RuneCountInString(s, 0, len(s))+1
に変更されました。これは、文字列中のruneの間にセパレータが存在すると見なすというロジックを、新しいutf8
パッケージの関数に適合させたものです。
src/lib/strings_test.go
の変更
TestUtflen
の削除:utflen
関数が削除されたため、そのテスト関数も不要となり削除されました。
src/lib/utf8.go
の変更
DecodeRuneInStringInternal
のシグネチャ変更:func DecodeRuneInStringInternal(s string, i int, n int) (rune, size int, short bool)
この関数は、文字列s
のインデックスi
から始まるn
バイトの範囲でUTF-8 runeをデコードします。n
パラメータが追加されたことで、デコード対象の範囲を明示的に指定できるようになり、部分的な文字列に対するデコードの正確性が向上しました。FullRuneInString
とDecodeRuneInString
の更新: これらの関数は、内部的にDecodeRuneInStringInternal
を呼び出す際に、len(s) - i
をn
パラメータとして渡すように変更されました。これにより、文字列の残りのバイト数を正確にDecodeRuneInStringInternal
に伝えることができます。RuneCount
関数の追加:
この関数は、バイトスライスexport func RuneCount(p *[]byte) int { i := 0; var n int; for n = 0; i < len(p); n++ { if p[i] < RuneSelf { // ASCII文字の場合 i++; } else { // マルチバイト文字の場合 rune, size := DecodeRune(p[i:len(p)]); i += size; } } return n; }
p
を走査し、各runeのバイトサイズを考慮しながらruneの数をカウントします。RuneSelf
はASCII文字の最大値(128)を表す定数で、これより小さいバイト値は1バイトのASCII文字であることを示します。それ以外の場合はDecodeRune
を使用してruneのバイトサイズを正確に取得し、インデックスを進めます。RuneCountInString
関数の追加:
この関数は、文字列export func RuneCountInString(s string, i int, l int) int { ei := i + l; // 終了インデックス n := 0; for n = 0; i < ei; n++ { if s[i] < RuneSelf { // ASCII文字の場合 i++; } else { // マルチバイト文字の場合 rune, size, short := DecodeRuneInStringInternal(s, i, ei - i); i += size; } } return n; }
s
の指定された範囲(i
からei
まで)でruneの数をカウントします。基本的なロジックはRuneCount
と同様ですが、文字列を対象とし、DecodeRuneInStringInternal
を利用してデコードを行います。ei - i
は、現在の位置から範囲の終わりまでの残りのバイト数を示します。
src/lib/utf8_test.go
の変更
TestRuneCount
の追加: このテスト関数は、RuneCount
とRuneCountInString
の正確性を検証するために追加されました。様々なテストケース(ASCII文字列、マルチバイト文字を含む文字列、不正なUTF-8シーケンスを含む文字列)が用意されており、それぞれの入力に対して期待されるrune数が返されることを確認しています。これにより、新しい関数の信頼性が保証されます。
関連リンク
- Go言語の
strings
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/strings - Go言語の
unicode/utf8
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/unicode/utf8 - Go言語における文字列、バイト、runeの概念に関する公式ブログ記事 (例: "Strings, bytes, runes and characters in Go"): https://go.dev/blog/strings (これはコミット当時のものではありませんが、概念理解に役立ちます)
参考にした情報源リンク
- Go言語の公式ドキュメント (特に
strings
とunicode/utf8
パッケージ) - Go言語のソースコード (コミット履歴と関連ファイル)
- UTF-8エンコーディングに関する一般的な情報源 (例: Wikipedia)
- Go言語における文字列処理に関する技術ブログや解説記事