[インデックス 1218] ファイルの概要
このコミットは、Go言語の標準ライブラリにUTF-8エンコーディング/デコーディングルーチンを導入し、テストフレームワークの改善(コマンドライン引数のパース機能追加)と、src/lib
ディレクトリにおけるテスト実行の自動化を目的としています。Go言語の初期段階において、多言語対応の基盤を築き、開発効率を高めるための重要な一歩と言えます。
コミット
commit 5169bb44e6bafe990112fa39890fef7168ae679f
Author: Russ Cox <rsc@golang.org>
Date: Fri Nov 21 16:13:31 2008 -0800
utf8 routines in go; a start.
also:
* parse flags in testing.Main.
* add make test in src/lib.
R=r
DELTA=323 (323 added, 0 deleted, 0 changed)
OCL=19831
CL=19850
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5169bb44e6bafe990112fa39890fef7168ae679f
元コミット内容
utf8 routines in go; a start.
also:
* parse flags in testing.Main.
* add make test in src/lib.
変更の背景
このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の初期開発段階でした。Go言語は、システムプログラミング言語として設計されており、ネットワークサービスや大規模な分散システムでの利用が想定されていました。このような用途では、国際化(i18n)と多言語対応が不可欠であり、特にテキスト処理においてUTF-8のサポートは基本的な要件となります。
当時のGo言語には、まだ標準でUTF-8を扱うための堅牢なライブラリが存在していませんでした。そのため、このコミットは、Go言語が多様な文字セットを正確に処理できるようにするための基盤を構築することを目的としています。具体的には、UTF-8バイト列からUnicodeコードポイント(rune)へのデコード、およびその逆のエンコード機能を提供します。
また、テストフレームワークの改善も重要な背景です。Go言語はテストを言語設計の中心に据えており、go test
コマンドとtesting
パッケージはGo開発の重要な部分を占めます。testing.Main
にコマンドライン引数(フラグ)のパース機能を追加することで、テストの実行方法をより柔軟に制御できるようになり、開発者が特定のテストのみを実行したり、テストの挙動を調整したりする際に役立ちます。
さらに、src/lib
ディレクトリにmake test
ターゲットを追加し、run.bash
スクリプトからそれを呼び出すようにしたことは、Go言語のビルドおよびテストシステム全体の自動化と効率化を推進する意図があります。これにより、ライブラリの変更が適切にテストされていることを保証し、継続的インテグレーションの基盤を強化します。
前提知識の解説
UTF-8
UTF-8(Unicode Transformation Format - 8-bit)は、Unicode文字を可変長バイト列でエンコードするための文字エンコーディング方式です。以下の特徴を持ちます。
- 可変長エンコーディング: 1文字を1バイトから4バイトで表現します。
- ASCII文字(U+0000からU+007F)は1バイトで表現され、従来のASCIIと互換性があります。これは、UTF-8が広く採用される大きな理由の一つです。
- その他の文字は2バイト以上で表現されます。
- 自己同期性: バイト列の途中からでも文字の境界を特定しやすい特性を持ちます。これは、不正なバイト列をスキップして次の有効な文字から処理を再開する際に役立ちます。
- バイトオーダーマーク(BOM)不要: UTF-8はバイトオーダーが明確に定義されているため、BOMは通常不要です。
- Unicodeコードポイント: Unicodeは、世界中のあらゆる文字に一意の番号(コードポイント)を割り当てています。Go言語では、これらのコードポイントを
rune
型(int32
のエイリアス)で表現します。
UTF-8のエンコーディングルールは以下の通りです。
Unicode範囲 (Hex) | UTF-8バイト列 (Binary) |
---|---|
U+0000 - U+007F | 0xxxxxxx |
U+0080 - U+07FF | 110xxxxx 10xxxxxx |
U+0800 - U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
U+10000 - U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
ここで、x
はUnicodeコードポイントのビットを表します。
Go言語のtesting
パッケージ
Go言語の標準ライブラリには、テストを記述するためのtesting
パッケージが用意されています。
go test
コマンド: Goのテストは、go test
コマンドを実行することで自動的に発見され、実行されます。テストファイルは通常、テスト対象のファイルと同じディレクトリに_test.go
というサフィックスを付けて配置されます。*testing.T
: テスト関数はfunc TestXxx(t *testing.T)
というシグネチャを持ち、*testing.T
型の引数を通じてテストの失敗を報告したり、ログを出力したりします。testing.Main
:testing
パッケージのMain
関数は、テストの実行を制御するエントリポイントです。通常、go test
コマンドが内部的にこの関数を呼び出します。このコミット以前は、Main
関数はコマンドライン引数を直接パースする機能を持っていませんでした。
flag
パッケージ
Go言語の標準ライブラリには、コマンドライン引数をパースするためのflag
パッケージが用意されています。これにより、アプリケーションやツールがコマンドラインから設定を受け取ることができます。
- フラグの定義:
flag.StringVar
,flag.IntVar
,flag.BoolVar
などを使用して、文字列、整数、ブール値などのフラグを定義します。 - フラグのパース:
flag.Parse()
関数を呼び出すことで、定義されたフラグとコマンドライン引数を関連付け、値をパースします。
Makefile
とmake
コマンド
Makefile
は、ソフトウェアのビルドプロセスを自動化するためのファイルです。make
コマンドは、Makefile
に記述されたルールに基づいて、ファイルのコンパイル、リンク、テスト実行などのタスクを実行します。
- ターゲット:
Makefile
には、実行可能なタスク(例:all
,clean
,test
)が定義されます。 - 依存関係: 各ターゲットは、それが依存する他のファイルやターゲットを指定できます。
- コマンド: ターゲットが実行されたときに実行されるシェルコマンドが記述されます。
このコミットでは、src/lib/Makefile
にtest
ターゲットが追加され、Go言語のライブラリのテストをmake
コマンド経由で実行できるようになっています。
技術的詳細
UTF-8ルーチンの実装 (src/lib/utf8.go
)
このコミットで追加されたsrc/lib/utf8.go
は、Go言語におけるUTF-8処理の初期実装を提供します。主要な関数は以下の通りです。
RuneError
: 不正なUTF-8シーケンスをデコードした際に返されるUnicode置換文字(U+FFFD)を定義します。RuneSelf
: 1バイトUTF-8シーケンスの最大値(0x80)を定義します。これより小さい値はASCII文字です。RuneMax
: Unicodeの最大コードポイント(U+10FFFF)を定義します。DecodeRuneInternal(p *[]byte) (rune, size int, short bool)
:- バイトスライス
p
の先頭から1つのUTF-8文字をデコードします。 - デコードされた
rune
(Unicodeコードポイント)、その文字が占めるバイト数size
、および入力バイトスライスが短すぎて完全な文字をデコードできなかった場合にtrue
となるshort
フラグを返します。 - UTF-8のエンコーディングルールに従って、先頭バイトのパターンから文字の長さを判断し、後続の継続バイトが正しい形式であるかを検証します。
- 不正なシーケンスや不完全なシーケンスの場合には
RuneError
を返します。
- バイトスライス
FullRune(p *[]byte) bool
:- バイトスライス
p
が完全なUTF-8文字を含んでいるかどうかをチェックします。 DecodeRuneInternal
を呼び出し、short
フラグがfalse
であればtrue
を返します。
- バイトスライス
DecodeRune(p *[]byte) (rune, size int)
:DecodeRuneInternal
のラッパー関数で、short
フラグを返さずにrune
とsize
のみを返します。
RuneLen(rune int) int
:- 与えられた
rune
がUTF-8でエンコードされた場合に何バイトになるかを返します。 - Unicodeの範囲に基づいて1バイトから4バイトの長さを決定します。
- 与えられた
EncodeRune(rune int, p *[]byte) int
:rune
をUTF-8バイト列にエンコードし、結果をバイトスライスp
に書き込みます。- 書き込まれたバイト数を返します。
RuneMax
を超えるrune
や不正なrune
はRuneError
としてエンコードされます。
これらの関数は、UTF-8のバイトパターン(例: 0xxxxxxx
、110xxxxx
、10xxxxxx
など)をビットマスクとシフト演算を駆使して解析・生成することで、効率的なエンコード/デコードを実現しています。特に、DecodeRuneInternal
では、各バイトの先頭ビットパターンをチェックし、文字の長さと継続バイトの妥当性を検証することで、堅牢なデコード処理を行っています。
testing.Main
におけるフラグパース (src/lib/testing.go
)
src/lib/testing.go
のMain
関数にflag.Parse()
が追加されました。
export func Main(tests *[]Test) {
flag.Parse(); // この行が追加
ok := true;
if len(tests) == 0 {
println("gotest: warning: no tests to run");
この変更により、go test
コマンドが実行される際に、testing
パッケージが提供するテスト実行のメインループに入る前に、コマンドラインで指定されたフラグが自動的にパースされるようになります。これにより、例えば-v
(詳細出力)、-run
(特定のテストの実行)、-bench
(ベンチマークの実行)などのテスト関連のフラグが機能する基盤が作られました。これは、Goのテストフレームワークがより柔軟で強力なものになるための重要なステップです。
src/lib/Makefile
とsrc/run.bash
の変更
src/lib/Makefile
には、utf8
ライブラリの追加と、テスト実行のための新しいターゲットが追加されました。
# ...
FILES=\
sort\
strings\
testing\
+ utf8\
+\
+TEST=\
+\tutf8\
\
# ...
+test.files: $(addsuffix .test, $(TEST))\
\
# ...
+%.test: %.6
+\tgotest $*_test.go
+\
# ...
+test: test.files
FILES
変数にutf8
が追加され、utf8.go
がビルド対象のライブラリとして認識されるようになりました。TEST
変数にutf8
が追加され、utf8
ライブラリのテストが実行対象として指定されました。test.files
ターゲットは、TEST
変数にリストされた各ライブラリに対して.test
サフィックスを持つターゲットを生成します。%.test
ルールは、%.6
(コンパイル済みGoバイナリ)に依存し、gotest $*_test.go
コマンドを実行します。gotest
は、Goのテストバイナリを実行するための内部コマンドです。test
ターゲットは、test.files
に依存し、すべての指定されたライブラリのテストを実行します。
src/run.bash
には、src/lib
のテストを実行するための行が追加されました。
# ...
+(xcd lib; make test) || exit $?
# ...
この行は、src/lib
ディレクトリに移動し、そこでmake test
コマンドを実行することを意味します。|| exit $?
は、make test
が失敗した場合にスクリプトの実行を停止するためのものです。これにより、Go言語全体のビルドおよびテストプロセスの一部として、src/lib
内の新しいUTF-8ルーチンのテストが自動的に実行されるようになりました。これは、変更がシステム全体に統合され、品質が保証されるための重要な自動化ステップです。
コアとなるコードの変更箇所
src/lib/Makefile
--- a/src/lib/Makefile
+++ b/src/lib/Makefile
@@ -30,11 +30,16 @@ FILES=\
sort\
strings\
testing\
+ utf8\
+\
+TEST=\
+\tutf8\
\
clean.dirs: $(addsuffix .dirclean, $(DIRS))\
install.dirs: $(addsuffix .dirinstall, $(DIRS))\
install.files: $(addsuffix .install, $(FILES))\
nuke.dirs: $(addsuffix .dirnuke, $(DIRS))\
+test.files: $(addsuffix .test, $(TEST))\
\
%.6: container/%.go
$(GC) container/$*.go
@@ -42,6 +47,9 @@ nuke.dirs: $(addsuffix .dirnuke, $(DIRS))\
%.6: %.go
$(GC) $*.go
\
+%.test: %.6
+\tgotest $*_test.go
+\
%.clean:\
rm -f $*.6
\
@@ -67,6 +75,8 @@ install: install.dirs install.files
nuke: nuke.dirs clean.files
rm -f $(GOROOT)/pkg/*
\
+test: test.files
+\
# TODO: dependencies - should auto-generate
\
bignum.6: fmt.dirinstall
src/lib/testing.go
--- a/src/lib/testing.go
+++ b/src/lib/testing.go
@@ -83,6 +83,7 @@ func TRunner(t *T, test *Test) {
}
export func Main(tests *[]Test) {
+\tflag.Parse();
\tok := true;
\tif len(tests) == 0 {
\t\tprintln("gotest: warning: no tests to run");
src/lib/utf8.go
(新規ファイル)
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// UTF-8 support.
package utf8
export const (
RuneError = 0xFFFD;
RuneSelf = 0x80;
RuneMax = 1<<21 - 1;
)
const (
T1 = 0x00; // 0000 0000
Tx = 0x80; // 1000 0000
T2 = 0xC0; // 1100 0000
T3 = 0xE0; // 1110 0000
T4 = 0xF0; // 1111 0000
T5 = 0xF8; // 1111 1000
Maskx = 0x3F; // 0011 1111
Mask2 = 0x1F; // 0001 1111
Mask3 = 0x0F; // 0000 1111
Mask4 = 0x07; // 0000 0111
Rune1Max = 1<<7 - 1;
Rune2Max = 1<<11 - 1;
Rune3Max = 1<<16 - 1;
Rune4Max = 1<<21 - 1;
)
func DecodeRuneInternal(p *[]byte) (rune, size int, short bool) {
if len(p) < 1 {
return RuneError, 0, true;
}
c0 := p[0];
// 1-byte, 7-bit sequence?
if c0 < Tx {
return int(c0), 1, false
}
// unexpected continuation byte?
if c0 < T2 {
return RuneError, 1, false
}
// need first continuation byte
if len(p) < 2 {
return RuneError, 1, true
}
c1 := p[1];
if c1 < Tx || T2 <= c1 {
return RuneError, 1, false
}
// 2-byte, 11-bit sequence?
if c0 < T3 {
rune = int(c0&Mask2)<<6 | int(c1&Maskx);
if rune <= Rune1Max {
return RuneError, 1, false
}
return rune, 2, false
}
// need second continuation byte
if len(p) < 3 {
return RuneError, 1, true
}
c2 := p[2];
if c2 < Tx || T2 <= c2 {
return RuneError, 1, false
}
// 3-byte, 16-bit sequence?
if c0 < T4 {
rune = int(c0&Mask3)<<12 | int(c1&Maskx)<<6 | int(c2&Maskx);
if rune <= Rune2Max {
return RuneError, 1, false
}
return rune, 3, false
}
// need third continuation byte
if len(p) < 4 {
return RuneError, 1, true
}
c3 := p[3];
if c3 < Tx || T2 <= c3 {
return RuneError, 1, false
}
// 4-byte, 21-bit sequence?
if c0 < T5 {
rune = int(c0&Mask4)<<18 | int(c1&Maskx)<<12 | int(c2&Maskx)<<6 | int(c3&Maskx);
if rune <= Rune3Max {
return RuneError, 1, false
}
return rune, 4, false
}
// error
return RuneError, 1, false
}
export func FullRune(p *[]byte) bool {
rune, size, short := DecodeRuneInternal(p);
return !short
}
export func DecodeRune(p *[]byte) (rune, size int) {
var short bool;
rune, size, short = DecodeRuneInternal(p);
return;
}
export func RuneLen(rune int) int {
switch {
case rune <= Rune1Max:
return 1;
case rune <= Rune2Max:
return 2;
case rune <= Rune3Max:
return 3;
case rune <= Rune4Max:
return 4;
}
return -1;
}
export func EncodeRune(rune int, p *[]byte) int {
if rune <= Rune1Max {
p[0] = byte(rune);
return 1;
}
if rune <= Rune2Max {
p[0] = T2 | byte(rune>>6);
p[1] = Tx | byte(rune)&Maskx;
return 2;
}
if rune > RuneMax {
rune = RuneError
}
if rune <= Rune3Max {
p[0] = T3 | byte(rune>>12);
p[1] = Tx | byte(rune>>6)&Maskx;
p[2] = Tx | byte(rune)&Maskx;
return 3;
}
p[0] = T4 | byte(rune>>18);
p[1] = Tx | byte(rune>>12)&Maskx;
p[2] = Tx | byte(rune>>6)&Maskx;
p[3] = Tx | byte(rune)&Maskx;
return 4;
}
src/lib/utf8_test.go
(新規ファイル)
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package utf8
import (
"fmt";
"syscall";
"testing";
"utf8";
)
type Utf8Map struct {
rune int;
str string;
}
var utf8map = []Utf8Map {
Utf8Map{ 0x0000, "\x00" },
Utf8Map{ 0x0001, "\x01" },
Utf8Map{ 0x007e, "\x7e" },
Utf8Map{ 0x007f, "\x7f" },
Utf8Map{ 0x0080, "\xc2\x80" },
Utf8Map{ 0x0081, "\xc2\x81" },
Utf8Map{ 0x00bf, "\xc2\xbf" },
Utf8Map{ 0x00c0, "\xc3\x80" },
Utf8Map{ 0x00c1, "\xc3\x81" },
Utf8Map{ 0x00c8, "\xc3\x88" },
Utf8Map{ 0x00d0, "\xc3\x90" },
Utf8Map{ 0x00e0, "\xc3\xa0" },
Utf8Map{ 0x00f0, "\xc3\xb0" },
Utf8Map{ 0x00f8, "\xc3\xb8" },
Utf8Map{ 0x00ff, "\xc3\xbf" },
Utf8Map{ 0x0100, "\xc4\x80" },
Utf8Map{ 0x07ff, "\xdf\xbf" },
Utf8Map{ 0x0800, "\xe0\xa0\x80" },
Utf8Map{ 0x0801, "\xe0\xa0\x81" },
Utf8Map{ 0xfffe, "\xef\xbf\xbe" },
Utf8Map{ 0xffff, "\xef\xbf\xbf" },
Utf8Map{ 0x10000, "\xf0\x90\x80\x80" },
Utf8Map{ 0x10001, "\xf0\x90\x80\x81" },
Utf8Map{ 0x10fffe, "\xf4\x8f\xbf\xbe" },
Utf8Map{ 0x10ffff, "\xf4\x8f\xbf\xbf" },
}
func CEscape(s *[]byte) string {
t := "\"";
for i := 0; i < len(s); i++ {
switch {
case s[i] == '\\' || s[i] == '"':
t += `\`;
t += string(s[i]);
case s[i] == '\n':
t += `\n`;
case s[i] == '\t':
t += `\t`;
case ' ' <= s[i] && s[i] <= '~':
t += string(s[i]);
default:
t += fmt.sprintf(`\x%02x`, s[i]);
}
}
t += "\"";
return t;
}
func Bytes(s string) *[]byte {
b := new([]byte, len(s)+1);
if !syscall.StringToBytes(b, s) {
panic("StringToBytes failed");
}
return b[0:len(s)];
}
export func TestFullRune(t *testing.T) {
for i := 0; i < len(utf8map); i++ {
m := utf8map[i];
b := Bytes(m.str);
if !utf8.FullRune(b) {
t.Errorf("FullRune(%s) (rune %04x) = false, want true", CEscape(b), m.rune);
}
if b1 := b[0:len(b)-1]; utf8.FullRune(b1) {
t.Errorf("FullRune(%s) = true, want false", CEscape(b1));
}
}
}
func EqualBytes(a, b *[]byte) bool {
if len(a) != len(b) {
return false;
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false;
}
}
return true;
}
export func TestEncodeRune(t *testing.T) {
for i := 0; i < len(utf8map); i++ {
m := utf8map[i];
b := Bytes(m.str);
var buf [10]byte;
n := utf8.EncodeRune(m.rune, &buf);
b1 := (&buf)[0:n];
if !EqualBytes(b, b1) {
t.Errorf("EncodeRune(0x%04x) = %s want %s", m.rune, CEscape(b1), CEscape(b));
}
}
}
export func TestDecodeRune(t *testing.T) {
for i := 0; i < len(utf8map); i++ {
m := utf8map[i];
b := Bytes(m.str);
rune, size := utf8.DecodeRune(b);
if rune != m.rune || size != len(b) {
t.Errorf("DecodeRune(%s) = 0x%04x, %d want 0x%04x, %d", CEscape(b), rune, size, m.rune, len(b));
}
// there's an extra byte that Bytes left behind - make sure trailing byte works
rune, size = utf8.DecodeRune(b[0:cap(b)]);
if rune != m.rune || size != len(b) {
t.Errorf("DecodeRune(%s) = 0x%04x, %d want 0x%04x, %d", CEscape(b), rune, size, m.rune, len(b));
}
// make sure missing bytes fail
rune, size = utf8.DecodeRune(b[0:len(b)-1]);
wantsize := 1;
if wantsize >= len(b) {
wantsize = 0;
}
if rune != RuneError || size != wantsize {
t.Errorf("DecodeRune(%s) = 0x%04x, %d want 0x%04x, %d", CEscape(b[0:len(b)-1]), rune, size, RuneError, wantsize);
}
// make sure bad sequences fail
if len(b) == 1 {
b[0] = 0x80;
} else {
b[len(b)-1] = 0x7F;
}
rune, size = utf8.DecodeRune(b);
if rune != RuneError || size != 1 {
t.Errorf("DecodeRune(%s) = 0x%04x, %d want 0x%04x, %d", CEscape(b), rune, size, RuneError, 1);
}
}
}
src/run.bash
--- a/src/run.bash
+++ b/src/run.bash
@@ -32,6 +32,8 @@ maketest \\\
# all of these are subtly different
# from what maketest does.
+(xcd lib; make test) || exit $?
+\
(xcd ../usr/gri/pretty
make clean
time make
コアとなるコードの解説
src/lib/utf8.go
このファイルは、Go言語におけるUTF-8エンコーディングとデコーディングの核心をなすロジックを含んでいます。
- 定数定義:
RuneError
,RuneSelf
,RuneMax
は、UTF-8処理における重要な境界値やエラー値を定義しています。特にRuneError
(U+FFFD) は、不正なバイトシーケンスが検出された際に返される標準的な置換文字です。 - バイトパターン定数:
T1
,Tx
,T2
,T3
,T4
,T5
は、UTF-8の各バイトの先頭ビットパターンを定義しており、文字の長さや種類を識別するために使用されます。例えば、T2
(0xC0,1100 0000
) は2バイト文字の先頭バイトのパターンを示します。 - マスク定数:
Maskx
,Mask2
,Mask3
,Mask4
は、UTF-8バイトからコードポイントのデータビットを抽出するためのビットマスクです。例えば、Maskx
(0x3F,0011 1111
) は継続バイトから下位6ビットを抽出するために使われます。 DecodeRuneInternal
関数:- この関数は、UTF-8デコードの主要なロジックを実装しています。
- 入力バイトスライス
p
の最初のバイトc0
を検査し、それが1バイト文字(ASCII)であるか、マルチバイト文字の先頭バイトであるかを判断します。 - マルチバイト文字の場合、必要な数の継続バイト(
c1
,c2
,c3
)を読み込み、それぞれのバイトがTx
(0x80,1000 0000
) からT2
(0xC0,1100 0000
) の範囲内にある(つまり、10xxxxxx
の形式である)ことを検証します。 - 各バイトから抽出したデータビットを適切な位置にシフトし、OR演算で結合することで、最終的なUnicodeコードポイント
rune
を構築します。 - デコード中にバイトスライスが不足した場合(
short
がtrue
)、または不正なバイトシーケンスが検出された場合(例: 継続バイトが期待される場所にない、オーバーロングエンコーディングなど)、RuneError
を返します。
EncodeRune
関数:- この関数は、Unicodeコードポイント
rune
をUTF-8バイト列にエンコードします。 rune
の値に基づいて、1バイト、2バイト、3バイト、または4バイトのどの形式でエンコードするかを決定します。- 各バイトは、適切な先頭ビットパターンと、
rune
から抽出したデータビットを結合して生成されます。 - 結果のバイト列は、出力バイトスライス
p
に書き込まれ、書き込まれたバイト数が返されます。
- この関数は、Unicodeコードポイント
これらの関数は、Go言語が文字列をUTF-8バイト列として扱うという設計思想の基盤を形成しています。
src/lib/testing.go
Main
関数へのflag.Parse()
の追加は、Goのテストフレームワークの柔軟性を大幅に向上させました。これにより、go test
コマンドに渡される-v
, -run
, -bench
などの標準的なテストフラグが、testing
パッケージ内で適切に解釈され、テストの実行挙動を制御できるようになります。これは、テストのデバッグ、特定のテストケースの実行、パフォーマンスベンチマークの実施など、開発者がテストをより効果的に利用するための重要な機能です。
src/lib/Makefile
とsrc/run.bash
これらのファイルへの変更は、Go言語のビルドシステムにおけるテストの自動化と統合を強化します。
Makefile
の変更により、src/lib
内の各ライブラリ(このコミットではutf8
)に対して、make test
コマンドでテストを実行できるようになりました。これは、ライブラリレベルでの単体テストの実行を標準化し、開発者が個々のコンポーネンの品質を容易に検証できるようにします。src/run.bash
への追加は、Go言語全体のビルドおよびテストプロセスの一部として、src/lib
のテストが自動的に実行されることを保証します。これは、継続的インテグレーション(CI)の初期段階であり、コードベース全体の健全性を維持するために不可欠です。新しいコードが追加された際に、既存のシステムとの互換性や機能の正しさを自動的に検証する仕組みが構築されました。
これらの変更は、Go言語の初期段階において、堅牢な標準ライブラリの構築、柔軟なテストフレームワークの提供、そして効率的な開発ワークフローの確立に向けた重要な基盤を築いたと言えます。
関連リンク
参考にした情報源リンク
- Go言語の公式リポジトリのコミット履歴
- Go言語の公式ドキュメント
- UTF-8に関する一般的な情報源(Wikipediaなど)
- Go言語の初期の設計に関する議論(Go言語のメーリングリストやデザインドキュメントなど、公開されている情報があれば)
make
コマンドとMakefile
に関する一般的な情報# [インデックス 1218] ファイルの概要
このコミットは、Go言語の標準ライブラリにUTF-8エンコーディング/デコーディングルーチンを導入し、テストフレームワークの改善(コマンドライン引数のパース機能追加)と、src/lib
ディレクトリにおけるテスト実行の自動化を目的としています。Go言語の初期段階において、多言語対応の基盤を築き、開発効率を高めるための重要な一歩と言えます。
コミット
commit 5169bb44e6bafe990112fa39890fef7168ae679f
Author: Russ Cox <rsc@golang.org>
Date: Fri Nov 21 16:13:31 2008 -0800
utf8 routines in go; a start.
also:
* parse flags in testing.Main.
* add make test in src/lib.
R=r
DELTA=323 (323 added, 0 deleted, 0 changed)
OCL=19831
CL=19850
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5169bb44e6bafe990112fa39890fef7168ae679f
元コミット内容
utf8 routines in go; a start.
also:
* parse flags in testing.Main.
* add make test in src/lib.
変更の背景
このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の初期開発段階でした。Go言語は、システムプログラミング言語として設計されており、ネットワークサービスや大規模な分散システムでの利用が想定されていました。このような用途では、国際化(i18n)と多言語対応が不可欠であり、特にテキスト処理においてUTF-8のサポートは基本的な要件となります。
当時のGo言語には、まだ標準でUTF-8を扱うための堅牢なライブラリが存在していませんでした。そのため、このコミットは、Go言語が多様な文字セットを正確に処理できるようにするための基盤を構築することを目的としています。具体的には、UTF-8バイト列からUnicodeコードポイント(rune)へのデコード、およびその逆のエンコード機能を提供します。
また、テストフレームワークの改善も重要な背景です。Go言語はテストを言語設計の中心に据えており、go test
コマンドとtesting
パッケージはGo開発の重要な部分を占めます。testing.Main
にコマンドライン引数(フラグ)のパース機能を追加することで、テストの実行方法をより柔軟に制御できるようになり、開発者が特定のテストのみを実行したり、テストの挙動を調整したりする際に役立ちます。
さらに、src/lib
ディレクトリにmake test
ターゲットを追加し、run.bash
スクリプトからそれを呼び出すようにしたことは、Go言語のビルドおよびテストシステム全体の自動化と効率化を推進する意図があります。これにより、ライブラリの変更が適切にテストされていることを保証し、継続的インテグレーションの基盤を強化します。
前提知識の解説
UTF-8
UTF-8(Unicode Transformation Format - 8-bit)は、Unicode文字を可変長バイト列でエンコードするための文字エンコーディング方式です。以下の特徴を持ちます。
- 可変長エンコーディング: 1文字を1バイトから4バイトで表現します。
- ASCII文字(U+0000からU+007F)は1バイトで表現され、従来のASCIIと互換性があります。これは、UTF-8が広く採用される大きな理由の一つです。
- その他の文字は2バイト以上で表現されます。
- 自己同期性: バイト列の途中からでも文字の境界を特定しやすい特性を持ちます。これは、不正なバイト列をスキップして次の有効な文字から処理を再開する際に役立ちます。
- バイトオーダーマーク(BOM)不要: UTF-8はバイトオーダーが明確に定義されているため、BOMは通常不要です。
- Unicodeコードポイント: Unicodeは、世界中のあらゆる文字に一意の番号(コードポイント)を割り当てています。Go言語では、これらのコードポイントを
rune
型(int32
のエイリアス)で表現します。
UTF-8のエンコーディングルールは以下の通りです。
Unicode範囲 (Hex) | UTF-8バイト列 (Binary) |
---|---|
U+0000 - U+007F | 0xxxxxxx |
U+0080 - U+07FF | 110xxxxx 10xxxxxx |
U+0800 - U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
U+10000 - U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
ここで、x
はUnicodeコードポイントのビットを表します。
Go言語のtesting
パッケージ
Go言語の標準ライブラリには、テストを記述するためのtesting
パッケージが用意されています。
go test
コマンド: Goのテストは、go test
コマンドを実行することで自動的に発見され、実行されます。テストファイルは通常、テスト対象のファイルと同じディレクトリに_test.go
というサフィックスを付けて配置されます。*testing.T
: テスト関数はfunc TestXxx(t *testing.T)
というシグネチャを持ち、*testing.T
型の引数を通じてテストの失敗を報告したり、ログを出力したりします。testing.Main
:testing
パッケージのMain
関数は、テストの実行を制御するエントリポイントです。通常、go test
コマンドが内部的にこの関数を呼び出します。このコミット以前は、Main
関数はコマンドライン引数を直接パースする機能を持っていませんでした。
flag
パッケージ
Go言語の標準ライブラリには、コマンドライン引数をパースするためのflag
パッケージが用意されています。これにより、アプリケーションやツールがコマンドラインから設定を受け取ることができます。
- フラグの定義:
flag.StringVar
,flag.IntVar
,flag.BoolVar
などを使用して、文字列、整数、ブール値などのフラグを定義します。 - フラグのパース:
flag.Parse()
関数を呼び出すことで、定義されたフラグとコマンドライン引数を関連付け、値をパースします。
Makefile
とmake
コマンド
Makefile
は、ソフトウェアのビルドプロセスを自動化するためのファイルです。make
コマンドは、Makefile
に記述されたルールに基づいて、ファイルのコンパイル、リンク、テスト実行などのタスクを実行します。
- ターゲット:
Makefile
には、実行可能なタスク(例:all
,clean
,test
)が定義されます。 - 依存関係: 各ターゲットは、それが依存する他のファイルやターゲットを指定できます。
- コマンド: ターゲットが実行されたときに実行されるシェルコマンドが記述されます。
このコミットでは、src/lib/Makefile
にtest
ターゲットが追加され、Go言語のライブラリのテストをmake
コマンド経由で実行できるようになっています。
技術的詳細
UTF-8ルーチンの実装 (src/lib/utf8.go
)
このコミットで追加されたsrc/lib/utf8.go
は、Go言語におけるUTF-8処理の初期実装を提供します。主要な関数は以下の通りです。
RuneError
: 不正なUTF-8シーケンスをデコードした際に返されるUnicode置換文字(U+FFFD)を定義します。RuneSelf
: 1バイトUTF-8シーケンスの最大値(0x80)を定義します。これより小さい値はASCII文字です。RuneMax
: Unicodeの最大コードポイント(U+10FFFF)を定義します。DecodeRuneInternal(p *[]byte) (rune, size int, short bool)
:- バイトスライス
p
の先頭から1つのUTF-8文字をデコードします。 - デコードされた
rune
(Unicodeコードポイント)、その文字が占めるバイト数size
、および入力バイトスライスが短すぎて完全な文字をデコードできなかった場合にtrue
となるshort
フラグを返します。 - UTF-8のエンコーディングルールに従って、先頭バイトのパターンから文字の長さを判断し、後続の継続バイトが正しい形式であるかを検証します。
- 不正なシーケンスや不完全なシーケンスの場合には
RuneError
を返します。
- バイトスライス
FullRune(p *[]byte) bool
:- バイトスライス
p
が完全なUTF-8文字を含んでいるかどうかをチェックします。 DecodeRuneInternal
を呼び出し、short
フラグがfalse
であればtrue
を返します。
- バイトスライス
DecodeRune(p *[]byte) (rune, size int)
:DecodeRuneInternal
のラッパー関数で、short
フラグを返さずにrune
とsize
のみを返します。
RuneLen(rune int) int
:- 与えられた
rune
がUTF-8でエンコードされた場合に何バイトになるかを返します。 - Unicodeの範囲に基づいて1バイトから4バイトの長さを決定します。
- 与えられた
EncodeRune(rune int, p *[]byte) int
:rune
をUTF-8バイト列にエンコードし、結果をバイトスライスp
に書き込みます。- 書き込まれたバイト数を返します。
RuneMax
を超えるrune
や不正なrune
はRuneError
としてエンコードされます。
これらの関数は、UTF-8のバイトパターン(例: 0xxxxxxx
、110xxxxx
、10xxxxxx
など)をビットマスクとシフト演算を駆使して解析・生成することで、効率的なエンコード/デコードを実現しています。特に、DecodeRuneInternal
では、各バイトの先頭ビットパターンをチェックし、文字の長さと継続バイトの妥当性を検証することで、堅牢なデコード処理を行っています。
testing.Main
におけるフラグパース (src/lib/testing.go
)
src/lib/testing.go
のMain
関数にflag.Parse()
が追加されました。
export func Main(tests *[]Test) {
flag.Parse(); // この行が追加
ok := true;
if len(tests) == 0 {
println("gotest: warning: no tests to run");
この変更により、go test
コマンドが実行される際に、testing
パッケージが提供するテスト実行のメインループに入る前に、コマンドラインで指定されたフラグが自動的にパースされるようになります。これにより、例えば-v
(詳細出力)、-run
(特定のテストの実行)、-bench
(ベンチマークの実行)などのテスト関連のフラグが機能する基盤が作られました。これは、Goのテストフレームワークがより柔軟で強力なものになるための重要なステップです。
src/lib/Makefile
とsrc/run.bash
の変更
src/lib/Makefile
には、utf8
ライブラリの追加と、テスト実行のための新しいターゲットが追加されました。
# ...
FILES=\
sort\
strings\
testing\
+ utf8\
+\
+TEST=\
+\tutf8\
\
clean.dirs: $(addsuffix .dirclean, $(DIRS))\
install.dirs: $(addsuffix .dirinstall, $(DIRS))\
install.files: $(addsuffix .install, $(FILES))\
nuke.dirs: $(addsuffix .dirnuke, $(DIRS))\
+test.files: $(addsuffix .test, $(TEST))\
\
%.6: container/%.go
$(GC) container/$*.go
@@ -42,6 +47,9 @@ nuke.dirs: $(addsuffix .dirnuke, $(DIRS))\
%.6: %.go
$(GC) $*.go
\
+%.test: %.6
+\tgotest $*_test.go
+\
%.clean:\
rm -f $*.6
\
@@ -67,6 +75,8 @@ install: install.dirs install.files
nuke: nuke.dirs clean.files
rm -f $(GOROOT)/pkg/*
\
+test: test.files
+\
# TODO: dependencies - should auto-generate
\
bignum.6: fmt.dirinstall
src/run.bash
には、src/lib
のテストを実行するための行が追加されました。
# ...
+(xcd lib; make test) || exit $?
# ...
この行は、src/lib
ディレクトリに移動し、そこでmake test
コマンドを実行することを意味します。|| exit $?
は、make test
が失敗した場合にスクリプトの実行を停止するためのものです。これにより、Go言語全体のビルドおよびテストプロセスの一部として、src/lib
内の新しいUTF-8ルーチンのテストが自動的に実行されるようになりました。これは、変更がシステム全体に統合され、品質が保証されるための重要な自動化ステップです。
コアとなるコードの変更箇所
src/lib/Makefile
--- a/src/lib/Makefile
+++ b/src/lib/Makefile
@@ -30,11 +30,16 @@ FILES=\
sort\
strings\
testing\
+ utf8\
+\
+TEST=\
+\tutf8\
\
clean.dirs: $(addsuffix .dirclean, $(DIRS))\
install.dirs: $(addsuffix .dirinstall, $(DIRS))\
install.files: $(addsuffix .install, $(FILES))\
nuke.dirs: $(addsuffix .dirnuke, $(DIRS))\
+test.files: $(addsuffix .test, $(TEST))\
\
%.6: container/%.go
$(GC) container/$*.go
@@ -42,6 +47,9 @@ nuke.dirs: $(addsuffix .dirnuke, $(DIRS))\
%.6: %.go
$(GC) $*.go
\
+%.test: %.6
+\tgotest $*_test.go
+\
%.clean:\
rm -f $*.6
\
@@ -67,6 +75,8 @@ install: install.dirs install.files
nuke: nuke.dirs clean.files
rm -f $(GOROOT)/pkg/*
\
+test: test.files
+\
# TODO: dependencies - should auto-generate
\
bignum.6: fmt.dirinstall
src/lib/testing.go
--- a/src/lib/testing.go
+++ b/src/lib/testing.go
@@ -83,6 +83,7 @@ func TRunner(t *T, test *Test) {
}
export func Main(tests *[]Test) {
+\tflag.Parse();
\tok := true;
\tif len(tests) == 0 {
\t\tprintln("gotest: warning: no tests to run");
src/lib/utf8.go
(新規ファイル)
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// UTF-8 support.
package utf8
export const (
RuneError = 0xFFFD;
RuneSelf = 0x80;
RuneMax = 1<<21 - 1;
)
const (
T1 = 0x00; // 0000 0000
Tx = 0x80; // 1000 0000
T2 = 0xC0; // 1100 0000
T3 = 0xE0; // 1110 0000
T4 = 0xF0; // 1111 0000
T5 = 0xF8; // 1111 1000
Maskx = 0x3F; // 0011 1111
Mask2 = 0x1F; // 0001 1111
Mask3 = 0x0F; // 0000 1111
Mask4 = 0x07; // 0000 0111
Rune1Max = 1<<7 - 1;
Rune2Max = 1<<11 - 1;
Rune3Max = 1<<16 - 1;
Rune4Max = 1<<21 - 1;
)
func DecodeRuneInternal(p *[]byte) (rune, size int, short bool) {
if len(p) < 1 {
return RuneError, 0, true;
}
c0 := p[0];
// 1-byte, 7-bit sequence?
if c0 < Tx {
return int(c0), 1, false
}
// unexpected continuation byte?
if c0 < T2 {
return RuneError, 1, false
}
// need first continuation byte
if len(p) < 2 {
return RuneError, 1, true
}
c1 := p[1];
if c1 < Tx || T2 <= c1 {
return RuneError, 1, false
}
// 2-byte, 11-bit sequence?
if c0 < T3 {
rune = int(c0&Mask2)<<6 | int(c1&Maskx);
if rune <= Rune1Max {
return RuneError, 1, false
}
return rune, 2, false
}
// need second continuation byte
if len(p) < 3 {
return RuneError, 1, true
}
c2 := p[2];
if c2 < Tx || T2 <= c2 {
return RuneError, 1, false
}
// 3-byte, 16-bit sequence?
if c0 < T4 {
rune = int(c0&Mask3)<<12 | int(c1&Maskx)<<6 | int(c2&Maskx);
if rune <= Rune2Max {
return RuneError, 1, false
}
return rune, 3, false
}
// need third continuation byte
if len(p) < 4 {
return RuneError, 1, true
}
c3 := p[3];
if c3 < Tx || T2 <= c3 {
return RuneError, 1, false
}
// 4-byte, 21-bit sequence?
if c0 < T5 {
rune = int(c0&Mask4)<<18 | int(c1&Maskx)<<12 | int(c2&Maskx)<<6 | int(c3&Maskx);
if rune <= Rune3Max {
return RuneError, 1, false
}
return rune, 4, false
}
// error
return RuneError, 1, false
}
export func FullRune(p *[]byte) bool {
rune, size, short := DecodeRuneInternal(p);
return !short
}
export func DecodeRune(p *[]byte) (rune, size int) {
var short bool;
rune, size, short = DecodeRuneInternal(p);
return;
}
export func RuneLen(rune int) int {
switch {
case rune <= Rune1Max:
return 1;
case rune <= Rune2Max:
return 2;
case rune <= Rune3Max:
return 3;
case rune <= Rune4Max:
return 4;
}
return -1;
}
export func EncodeRune(rune int, p *[]byte) int {
if rune <= Rune1Max {
p[0] = byte(rune);
return 1;
}
if rune <= Rune2Max {
p[0] = T2 | byte(rune>>6);
p[1] = Tx | byte(rune)&Maskx;
return 2;
}
if rune > RuneMax {
rune = RuneError
}
if rune <= Rune3Max {
p[0] = T3 | byte(rune>>12);
p[1] = Tx | byte(rune>>6)&Maskx;
p[2] = Tx | byte(rune)&Maskx;
return 3;
}
p[0] = T4 | byte(rune>>18);
p[1] = Tx | byte(rune>>12)&Maskx;
p[2] = Tx | byte(rune>>6)&Maskx;
p[3] = Tx | byte(rune)&Maskx;
return 4;
}
src/lib/utf8_test.go
(新規ファイル)
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package utf8
import (
"fmt";
"syscall";
"testing";
"utf8";
)
type Utf8Map struct {
rune int;
str string;
}
var utf8map = []Utf8Map {
Utf8Map{ 0x0000, "\x00" },
Utf8Map{ 0x0001, "\x01" },
Utf8Map{ 0x007e, "\x7e" },
Utf8Map{ 0x007f, "\x7f" },
Utf8Map{ 0x0080, "\xc2\x80" },
Utf8Map{ 0x0081, "\xc2\x81" },
Utf8Map{ 0x00bf, "\xc2\xbf" },
Utf8Map{ 0x00c0, "\xc3\x80" },
Utf8Map{ 0x00c1, "\xc3\x81" },
Utf8Map{ 0x00c8, "\xc3\x88" },
Utf8Map{ 0x00d0, "\xc3\x90" },
Utf8Map{ 0x00e0, "\xc3\xa0" },
Utf8Map{ 0x00f0, "\xc3\xb0" },
Utf8Map{ 0x00f8, "\xc3\xb8" },
Utf8Map{ 0x00ff, "\xc3\xbf" },
Utf8Map{ 0x0100, "\xc4\x80" },
Utf8Map{ 0x07ff, "\xdf\xbf" },
Utf8Map{ 0x0800, "\xe0\xa0\x80" },
Utf8Map{ 0x0801, "\xe0\xa0\x81" },
Utf8Map{ 0xfffe, "\xef\xbf\xbe" },
Utf8Map{ 0xffff, "\xef\xbf\xbf" },
Utf8Map{ 0x10000, "\xf0\x90\x80\x80" },
Utf8Map{ 0x10001, "\xf0\x90\x80\x81" },
Utf8Map{ 0x10fffe, "\xf4\x8f\xbf\xbe" },
Utf8Map{ 0x10ffff, "\xf4\x8f\xbf\xbf" },
}
func CEscape(s *[]byte) string {
t := "\"";
for i := 0; i < len(s); i++ {
switch {
case s[i] == '\\' || s[i] == '"':
t += `\`;
t += string(s[i]);
case s[i] == '\n':
t += `\n`;
case s[i] == '\t':
t += `\t`;
case ' ' <= s[i] && s[i] <= '~':
t += string(s[i]);
default:
t += fmt.sprintf(`\x%02x`, s[i]);
}
}
t += "\"";
return t;
}
func Bytes(s string) *[]byte {
b := new([]byte, len(s)+1);
if !syscall.StringToBytes(b, s) {
panic("StringToBytes failed");
}
return b[0:len(s)];
}
export func TestFullRune(t *testing.T) {
for i := 0; i < len(utf8map); i++ {
m := utf8map[i];
b := Bytes(m.str);
if !utf8.FullRune(b) {
t.Errorf("FullRune(%s) (rune %04x) = false, want true", CEscape(b), m.rune);
}
if b1 := b[0:len(b)-1]; utf8.FullRune(b1) {
t.Errorf("FullRune(%s) = true, want false", CEscape(b1));
}
}
}
func EqualBytes(a, b *[]byte) bool {
if len(a) != len(b) {
return false;
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false;
}
}
return true;
}
export func TestEncodeRune(t *testing.T) {
for i := 0; i < len(utf8map); i++ {
m := utf8map[i];
b := Bytes(m.str);
var buf [10]byte;
n := utf8.EncodeRune(m.rune, &buf);
b1 := (&buf)[0:n];
if !EqualBytes(b, b1) {
t.Errorf("EncodeRune(0x%04x) = %s want %s", m.rune, CEscape(b1), CEscape(b));
}
}
}
export func TestDecodeRune(t *testing.T) {
for i := 0; i < len(utf8map); i++ {
m := utf8map[i];
b := Bytes(m.str);
rune, size := utf8.DecodeRune(b);
if rune != m.rune || size != len(b) {
t.Errorf("DecodeRune(%s) = 0x%04x, %d want 0x%04x, %d", CEscape(b), rune, size, m.rune, len(b));
}
// there's an extra byte that Bytes left behind - make sure trailing byte works
rune, size = utf8.DecodeRune(b[0:cap(b)]);
if rune != m.rune || size != len(b) {
t.Errorf("DecodeRune(%s) = 0x%04x, %d want 0x%04x, %d", CEscape(b), rune, size, m.rune, len(b));
}
// make sure missing bytes fail
rune, size = utf8.DecodeRune(b[0:len(b)-1]);
wantsize := 1;
if wantsize >= len(b) {
wantsize = 0;
}
if rune != RuneError || size != wantsize {
t.Errorf("DecodeRune(%s) = 0x%04x, %d want 0x%04x, %d", CEscape(b[0:len(b)-1]), rune, size, RuneError, wantsize);
}
// make sure bad sequences fail
if len(b) == 1 {
b[0] = 0x80;
} else {
b[len(b)-1] = 0x7F;
}
rune, size = utf8.DecodeRune(b);
if rune != RuneError || size != 1 {
t.Errorf("DecodeRune(%s) = 0x%04x, %d want 0x%04x, %d", CEscape(b), rune, size, RuneError, 1);
}
}
}
src/run.bash
--- a/src/run.bash
+++ b/src/run.bash
@@ -32,6 +32,8 @@ maketest \\\
# all of these are subtly different
# from what maketest does.
+(xcd lib; make test) || exit $?
+\
(xcd ../usr/gri/pretty
make clean
time make
コアとなるコードの解説
src/lib/utf8.go
このファイルは、Go言語におけるUTF-8エンコーディングとデコーディングの核心をなすロジックを含んでいます。
- 定数定義:
RuneError
,RuneSelf
,RuneMax
は、UTF-8処理における重要な境界値やエラー値を定義しています。特にRuneError
(U+FFFD) は、不正なバイトシーケンスが検出された際に返される標準的な置換文字です。 - バイトパターン定数:
T1
,Tx
,T2
,T3
,T4
,T5
は、UTF-8の各バイトの先頭ビットパターンを定義しており、文字の長さや種類を識別するために使用されます。例えば、T2
(0xC0,1100 0000
) は2バイト文字の先頭バイトのパターンを示します。 - マスク定数:
Maskx
,Mask2
,Mask3
,Mask4
は、UTF-8バイトからコードポイントのデータビットを抽出するためのビットマスクです。例えば、Maskx
(0x3F,0011 1111
) は継続バイトから下位6ビットを抽出するために使われます。 DecodeRuneInternal
関数:- この関数は、UTF-8デコードの主要なロジックを実装しています。
- 入力バイトスライス
p
の最初のバイトc0
を検査し、それが1バイト文字(ASCII)であるか、マルチバイト文字の先頭バイトであるかを判断します。 - マルチバイト文字の場合、必要な数の継続バイト(
c1
,c2
,c3
)を読み込み、それぞれのバイトがTx
(0x80,1000 0000
) からT2
(0xC0,1100 0000
) の範囲内にある(つまり、10xxxxxx
の形式である)ことを検証します。 - 各バイトから抽出したデータビットを適切な位置にシフトし、OR演算で結合することで、最終的なUnicodeコードポイント
rune
を構築します。 - デコード中にバイトスライスが不足した場合(
short
がtrue
)、または不正なバイトシーケンスが検出された場合(例: 継続バイトが期待される場所にない、オーバーロングエンコーディングなど)、RuneError
を返します。
EncodeRune
関数:- この関数は、Unicodeコードポイント
rune
をUTF-8バイト列にエンコードします。 rune
の値に基づいて、1バイト、2バイト、3バイト、または4バイトのどの形式でエンコードするかを決定します。- 各バイトは、適切な先頭ビットパターンと、
rune
から抽出したデータビットを結合して生成されます。 - 結果のバイト列は、出力バイトスライス
p
に書き込まれ、書き込まれたバイト数が返されます。
- この関数は、Unicodeコードポイント
これらの関数は、Go言語が文字列をUTF-8バイト列として扱うという設計思想の基盤を形成しています。
src/lib/testing.go
Main
関数へのflag.Parse()
の追加は、Goのテストフレームワークの柔軟性を大幅に向上させました。これにより、go test
コマンドに渡される-v
, -run
, -bench
などの標準的なテストフラグが、testing
パッケージ内で適切に解釈され、テストの実行挙動を制御できるようになります。これは、テストのデバッグ、特定のテストケースの実行、パフォーマンスベンチマークの実施など、開発者がテストをより効果的に利用するための重要な機能です。
src/lib/Makefile
とsrc/run.bash
これらのファイルへの変更は、Go言語のビルドシステムにおけるテストの自動化と統合を強化します。
Makefile
の変更により、src/lib
内の各ライブラリ(このコミットではutf8
)に対して、make test
コマンドでテストを実行できるようになりました。これは、ライブラリレベルでの単体テストの実行を標準化し、開発者が個々のコンポーネントの品質を容易に検証できるようにします。src/run.bash
への追加は、Go言語全体のビルドおよびテストプロセスの一部として、src/lib
のテストが自動的に実行されることを保証します。これは、継続的インテグレーション(CI)の初期段階であり、コードベース全体の健全性を維持するために不可欠です。新しいコードが追加された際に、既存のシステムとの互換性や機能の正しさを自動的に検証する仕組みが構築されました。
これらの変更は、Go言語の初期段階において、堅牢な標準ライブラリの構築、柔軟なテストフレームワークの提供、そして効率的な開発ワークフローの確立に向けた重要な基盤を築いたと言えます。
関連リンク
参考にした情報源リンク
- Go言語の公式リポジトリのコミット履歴
- Go言語の公式ドキュメント
- UTF-8に関する一般的な情報源(Wikipediaなど)
- Go言語の初期の設計に関する議論(Go言語のメーリングリストやデザインドキュメントなど、公開されている情報があれば)
make
コマンドとMakefile
に関する一般的な情報