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

[インデックス 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+007F0xxxxxxx
U+0080 - U+07FF110xxxxx 10xxxxxx
U+0800 - U+FFFF1110xxxx 10xxxxxx 10xxxxxx
U+10000 - U+10FFFF11110xxx 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()関数を呼び出すことで、定義されたフラグとコマンドライン引数を関連付け、値をパースします。

Makefilemakeコマンド

Makefileは、ソフトウェアのビルドプロセスを自動化するためのファイルです。makeコマンドは、Makefileに記述されたルールに基づいて、ファイルのコンパイル、リンク、テスト実行などのタスクを実行します。

  • ターゲット: Makefileには、実行可能なタスク(例: all, clean, test)が定義されます。
  • 依存関係: 各ターゲットは、それが依存する他のファイルやターゲットを指定できます。
  • コマンド: ターゲットが実行されたときに実行されるシェルコマンドが記述されます。

このコミットでは、src/lib/Makefiletestターゲットが追加され、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フラグを返さずにrunesizeのみを返します。
  • RuneLen(rune int) int:
    • 与えられたruneがUTF-8でエンコードされた場合に何バイトになるかを返します。
    • Unicodeの範囲に基づいて1バイトから4バイトの長さを決定します。
  • EncodeRune(rune int, p *[]byte) int:
    • runeをUTF-8バイト列にエンコードし、結果をバイトスライスpに書き込みます。
    • 書き込まれたバイト数を返します。
    • RuneMaxを超えるruneや不正なruneRuneErrorとしてエンコードされます。

これらの関数は、UTF-8のバイトパターン(例: 0xxxxxxx110xxxxx10xxxxxxなど)をビットマスクとシフト演算を駆使して解析・生成することで、効率的なエンコード/デコードを実現しています。特に、DecodeRuneInternalでは、各バイトの先頭ビットパターンをチェックし、文字の長さと継続バイトの妥当性を検証することで、堅牢なデコード処理を行っています。

testing.Mainにおけるフラグパース (src/lib/testing.go)

src/lib/testing.goMain関数に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/Makefilesrc/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を構築します。
    • デコード中にバイトスライスが不足した場合(shorttrue)、または不正なバイトシーケンスが検出された場合(例: 継続バイトが期待される場所にない、オーバーロングエンコーディングなど)、RuneErrorを返します。
  • EncodeRune関数:
    • この関数は、UnicodeコードポイントruneをUTF-8バイト列にエンコードします。
    • runeの値に基づいて、1バイト、2バイト、3バイト、または4バイトのどの形式でエンコードするかを決定します。
    • 各バイトは、適切な先頭ビットパターンと、runeから抽出したデータビットを結合して生成されます。
    • 結果のバイト列は、出力バイトスライスpに書き込まれ、書き込まれたバイト数が返されます。

これらの関数は、Go言語が文字列をUTF-8バイト列として扱うという設計思想の基盤を形成しています。

src/lib/testing.go

Main関数へのflag.Parse()の追加は、Goのテストフレームワークの柔軟性を大幅に向上させました。これにより、go testコマンドに渡される-v, -run, -benchなどの標準的なテストフラグが、testingパッケージ内で適切に解釈され、テストの実行挙動を制御できるようになります。これは、テストのデバッグ、特定のテストケースの実行、パフォーマンスベンチマークの実施など、開発者がテストをより効果的に利用するための重要な機能です。

src/lib/Makefilesrc/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+007F0xxxxxxx
U+0080 - U+07FF110xxxxx 10xxxxxx
U+0800 - U+FFFF1110xxxx 10xxxxxx 10xxxxxx
U+10000 - U+10FFFF11110xxx 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()関数を呼び出すことで、定義されたフラグとコマンドライン引数を関連付け、値をパースします。

Makefilemakeコマンド

Makefileは、ソフトウェアのビルドプロセスを自動化するためのファイルです。makeコマンドは、Makefileに記述されたルールに基づいて、ファイルのコンパイル、リンク、テスト実行などのタスクを実行します。

  • ターゲット: Makefileには、実行可能なタスク(例: all, clean, test)が定義されます。
  • 依存関係: 各ターゲットは、それが依存する他のファイルやターゲットを指定できます。
  • コマンド: ターゲットが実行されたときに実行されるシェルコマンドが記述されます。

このコミットでは、src/lib/Makefiletestターゲットが追加され、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フラグを返さずにrunesizeのみを返します。
  • RuneLen(rune int) int:
    • 与えられたruneがUTF-8でエンコードされた場合に何バイトになるかを返します。
    • Unicodeの範囲に基づいて1バイトから4バイトの長さを決定します。
  • EncodeRune(rune int, p *[]byte) int:
    • runeをUTF-8バイト列にエンコードし、結果をバイトスライスpに書き込みます。
    • 書き込まれたバイト数を返します。
    • RuneMaxを超えるruneや不正なruneRuneErrorとしてエンコードされます。

これらの関数は、UTF-8のバイトパターン(例: 0xxxxxxx110xxxxx10xxxxxxなど)をビットマスクとシフト演算を駆使して解析・生成することで、効率的なエンコード/デコードを実現しています。特に、DecodeRuneInternalでは、各バイトの先頭ビットパターンをチェックし、文字の長さと継続バイトの妥当性を検証することで、堅牢なデコード処理を行っています。

testing.Mainにおけるフラグパース (src/lib/testing.go)

src/lib/testing.goMain関数に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/Makefilesrc/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を構築します。
    • デコード中にバイトスライスが不足した場合(shorttrue)、または不正なバイトシーケンスが検出された場合(例: 継続バイトが期待される場所にない、オーバーロングエンコーディングなど)、RuneErrorを返します。
  • EncodeRune関数:
    • この関数は、UnicodeコードポイントruneをUTF-8バイト列にエンコードします。
    • runeの値に基づいて、1バイト、2バイト、3バイト、または4バイトのどの形式でエンコードするかを決定します。
    • 各バイトは、適切な先頭ビットパターンと、runeから抽出したデータビットを結合して生成されます。
    • 結果のバイト列は、出力バイトスライスpに書き込まれ、書き込まれたバイト数が返されます。

これらの関数は、Go言語が文字列をUTF-8バイト列として扱うという設計思想の基盤を形成しています。

src/lib/testing.go

Main関数へのflag.Parse()の追加は、Goのテストフレームワークの柔軟性を大幅に向上させました。これにより、go testコマンドに渡される-v, -run, -benchなどの標準的なテストフラグが、testingパッケージ内で適切に解釈され、テストの実行挙動を制御できるようになります。これは、テストのデバッグ、特定のテストケースの実行、パフォーマンスベンチマークの実施など、開発者がテストをより効果的に利用するための重要な機能です。

src/lib/Makefilesrc/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に関する一般的な情報