[インデックス 1237] ファイルの概要
このコミットは、Go言語の標準ライブラリにおける複数の重要な改善と機能追加を含んでいます。主にfmt
パッケージのフォーマット機能の拡張、utf8
パッケージのUnicode文字処理の強化、reflect
パッケージのインターフェース操作の改善、そしてstrconv
パッケージへの文字列引用符付け機能の追加が行われています。
変更された主なファイルは以下の通りです。
src/lib/bufio.go
: バッファリングされたI/OにUnicodeルーンの読み取り機能を追加。src/lib/fmt/fmt_test.go
:fmt
パッケージの新しいフォーマット機能のためのテストスイート。src/lib/fmt/format.go
:fmt
パッケージの内部フォーマットロジック、特に数値、文字列のパディング、および新しいフォーマットフラグの処理を更新。src/lib/fmt/print.go
:fmt
パッケージのprintf
系関数の実装を更新し、新しいフォーマット動詞とリフレクションによる引数処理をサポート。src/lib/reflect/all_test.go
:reflect
パッケージのインターフェース関連の新しいテスト。src/lib/reflect/cast_amd64.s
: AMD64アーキテクチャ向けのリフレクション関連のアセンブリコードに、インターフェースポインタ変換関数を追加。src/lib/reflect/gencast.sh
: リフレクションの型生成スクリプトにInterface
型を追加。src/lib/reflect/value.go
:reflect
パッケージのValue
インターフェースとインターフェース値の取得方法を更新。src/lib/strconv/Makefile
:strconv
パッケージのビルド設定に新しいquote.go
ファイルを追加。src/lib/strconv/ftoa_test.go
: 浮動小数点数から文字列への変換テストに新しいケースを追加。src/lib/strconv/quote.go
: 文字列の引用符付けとバッククォート可能かどうかの判定を行う新しいユーティリティ関数群。src/lib/strconv/quote_test.go
:strconv/quote.go
のテストスイート。src/lib/utf8.go
: UTF-8エンコーディングの定数と、文字列からのルーンデコード機能を追加。src/lib/utf8_test.go
:utf8
パッケージのテストを更新し、新しい文字列デコード機能に対応。test/fmt_test.go
: 古いfmt
テストファイルが削除され、新しいsrc/lib/fmt/fmt_test.go
に置き換えられた。
コミット
commit 387df5e1763a5d400b1d0bf153b9d753eaea3471
Author: Russ Cox <rsc@golang.org>
Date: Mon Nov 24 14:51:33 2008 -0800
replay CL 19916 and CL 19913 now that the build can handle them
TBR=r
OCL=19924
CL=19934
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/387df5e1763a5d400b1d0bf153b9d753eaea3471
元コミット内容
このコミットメッセージにある「replay CL 19916 and CL 19913 now that the build can handle them」という記述は、以前に提出された変更リスト(Change List, CL)である19916と19913が、当時のビルドシステムや依存関係の問題により適用できなかったか、あるいは一時的に revert されていたことを示唆しています。そして、それらの問題が解決されたため、今回改めてこれらの変更を適用(replay)したことを意味します。これは、Go言語の初期開発段階において、コードベースの進化とビルドシステムの調整が並行して行われていた状況を反映しています。
変更の背景
このコミットが行われた2008年11月は、Go言語がまだ一般に公開される前の初期開発段階にありました。この時期のGo言語は、その設計思想(シンプルさ、並行性、効率性)を具体化するための基本的なライブラリとランタイムの構築に注力していました。
このコミットにおける変更の背景には、以下の主要な動機が考えられます。
- より堅牢なフォーマット機能の必要性:
fmt
パッケージは、Go言語における標準的なフォーマット済みI/Oを提供します。C言語のprintf
に似た機能を持つこのパッケージは、デバッグ出力、ログ記録、ユーザーへの表示など、あらゆる場面で利用されます。初期のfmt
パッケージは基本的な機能しか持っていなかったため、より柔軟で表現力豊かなフォーマット(例:数値のゼロパディング、文字列の引用符付け、16進数表示)が求められていました。特に、Go言語がシステムプログラミング言語としての側面を持つことを考えると、低レベルなデータ(バイト列など)の表現能力は重要でした。 - UnicodeとUTF-8の適切なサポート: Go言語は設計当初からUnicodeとUTF-8を第一級の市民として扱っています。しかし、初期の実装では、文字列とバイト列の間の変換や、UTF-8エンコードされた文字列からのルーン(Unicodeコードポイント)の効率的なデコードに関して、改善の余地がありました。特に、バッファリングされたI/O (
bufio
) において、バイトストリームから直接ルーンを読み取る機能は、多言語対応アプリケーションにとって不可欠です。 - リフレクションの進化:
reflect
パッケージは、実行時に型情報にアクセスし、値を操作するための強力な機能を提供します。fmt
パッケージのような汎用的なフォーマットライブラリは、引数の型を動的に検査し、それに応じて適切なフォーマットを適用するためにリフレクションを多用します。初期のreflect
パッケージはまだ発展途上であり、特にインターフェース型を介した値の取得や操作に関して、より洗練されたメカニズムが必要とされていました。 - 文字列ユーティリティの拡充: 文字列のパースや変換は、多くのアプリケーションで頻繁に行われる操作です。
strconv
パッケージはこれらの機能を提供しますが、文字列を安全に引用符付けしたり、バッククォートで表現できるかを判定する機能は、特にコード生成やデータシリアライズの文脈で有用です。
これらの変更は、Go言語がより実用的で、多様なアプリケーション開発に対応できるような、基本的なインフラストラクチャを強化する一環として行われました。
前提知識の解説
このコミットの変更内容を深く理解するためには、以下のGo言語の概念と関連技術についての知識が役立ちます。
- Go言語のパッケージシステム: Goのコードはパッケージに分割され、
import
文で他のパッケージの機能を利用します。fmt
,reflect
,strconv
,utf8
,bufio
はGoの標準ライブラリの一部です。 - Go言語の型システムとインターフェース: Goは静的型付け言語ですが、インターフェースを通じてポリモーフィズムを実現します。インターフェースはメソッドの集合を定義し、任意の型がそのメソッドを実装していれば、そのインターフェース型として扱えます。
interface{}
は「空のインターフェース」と呼ばれ、任意の型の値を保持できます。 - Go言語の文字列とバイト列: Goの文字列はUTF-8でエンコードされたバイト列であり、不変です。
string
型はバイト列として扱われますが、range
ループを使うとUnicodeのルーン(rune
型)としてイテレートできます。[]byte
は可変なバイトスライスです。 - UnicodeとUTF-8:
- Unicode: 世界中の文字を統一的に扱うための文字コード標準です。各文字には一意の「コードポイント」が割り当てられています。
- UTF-8: Unicodeコードポイントをバイト列にエンコードするための可変長エンコーディング方式です。ASCII文字は1バイトで表現され、非ASCII文字は2〜4バイトで表現されます。これにより、ASCIIとの互換性を保ちつつ、効率的に多言語を扱えます。
- ルーン (Rune): Go言語では、Unicodeのコードポイントを表現するために
rune
型(int32
のエイリアス)を使用します。
fmt
パッケージ: Go言語のフォーマット済みI/Oを提供するパッケージです。Printf
,Sprintf
,Fprint
などの関数があり、フォーマット文字列と引数を使って様々な型の値を整形して出力できます。フォーマット文字列は%
で始まる「フォーマット動詞」を含みます(例:%d
は整数、%s
は文字列)。reflect
パッケージ: 実行時にGoのプログラムの構造(型、値、メソッドなど)を検査・操作するための機能を提供します。reflect.Value
はGoの任意の値を抽象的に表現し、reflect.Type
はその値の型情報を提供します。bufio
パッケージ: バッファリングされたI/O操作を提供し、効率的な読み書きを可能にします。Reader
やWriter
などの型があります。strconv
パッケージ: 文字列と基本的なデータ型(数値、真偽値など)の間で変換を行う機能を提供します。- Goのアセンブリ言語: Go言語の一部は、パフォーマンスが重要な部分や、特定のハードウェア機能にアクセスするためにアセンブリ言語で書かれています。GoのアセンブリはPlan 9アセンブリの派生であり、一般的なx86/x64アセンブリとは異なる構文を持ちます。このコミットでは、
reflect
パッケージのインターフェース操作に関連するアセンブリコードが変更されています。 - テスト駆動開発 (TDD): Go言語の標準ライブラリ開発では、テストが非常に重視されます。
_test.go
ファイルに書かれたテストコードは、変更が正しく機能することを確認するための重要な役割を果たします。
技術的詳細
このコミットは、Go言語の基本的な機能であるフォーマット、Unicode処理、リフレクション、文字列変換にわたる広範な改善を導入しています。
1. fmt
パッケージの拡張
fmt
パッケージは、C言語のprintf
にインスパイアされた強力なフォーマット機能を提供します。このコミットでは、特に以下の点が強化されています。
- 新しいフォーマット動詞:
%x
,%X
(文字列/バイト列の16進数表現): 文字列やバイトスライスをそのバイト値の16進数表現として出力する機能が追加されました。%x
は小文字、%X
は大文字を使用します。これは、バイナリデータのデバッグや表示に非常に有用です。%q
(引用符付き文字列): 文字列をGoの文字列リテラル形式で引用符付けして出力する機能が追加されました。特殊文字はエスケープされ、必要に応じてバッククォート文字列(raw string literal)も使用されます。これは、文字列の内容を正確に表現したい場合に役立ちます。
- フォーマットフラグの強化:
Fmt
構造体にminus
,plus
,sharp
,space
,zero
といった新しいフラグが追加されました。これらはフォーマット文字列中の%
と動詞の間に指定される修飾子(例:%-10s
,%+d
,%#x
,% d
,%05d
)に対応します。minus
(-
): 左寄せ。plus
(+
): 数値に常に符号(+
または-
)を付ける。sharp
(#
): 別の形式(例: 16進数に0x
プレフィックス、%q
でバッククォート)。space
(zero
(0
): 数値のゼロパディング。- これらのフラグは、数値や文字列の出力形式をより細かく制御するために使用されます。特に、
integer
関数やpad
関数でこれらのフラグが考慮されるようになりました。
[]byte
の文字列としての扱い:print.go
のgetString
関数が*[]byte
型をstring
として扱えるように拡張されました。これにより、fmt
パッケージはバイトスライスを直接文字列としてフォーマットできるようになり、string(myBytes)
のような明示的な変換が不要になる場合があります。
2. utf8
パッケージの強化
Go言語はUTF-8をネイティブにサポートしていますが、このコミットではそのサポートがさらに強化されました。
RuneMax
とUTFMax
の更新:RuneMax
が1<<21 - 1
から0x10FFFF
に変更されました。これはUnicodeの最大コードポイント(U+10FFFF)を正確に反映しています。UTFMax
が4
に設定されました。これはUTF-8エンコーディングにおける1つのルーンの最大バイト長が4バイトであることを示します。
- 文字列からのルーンデコード:
DecodeRuneInStringInternal
,FullRuneInString
,DecodeRuneInString
といった関数が追加されました。これらは、バイトスライスではなく、Goのstring
型から直接UTF-8ルーンをデコードするためのものです。これにより、文字列操作におけるUTF-8の扱いがより効率的かつ安全になります。
bufio.ReadRune()
の追加:bufio
パッケージにReadRune()
メソッドが追加されました。これは、バッファリングされた入力ストリームから1つのUnicodeルーンを読み取るためのものです。内部的にはutf8
パッケージの機能を利用し、部分的なUTF-8シーケンスがバッファの終端にある場合でも、追加のバイトを読み込んで完全なルーンをデコードしようとします。
3. reflect
パッケージの改善
reflect
パッケージは、Goのプログラムが自身の構造を検査・操作することを可能にします。
interface{}
の汎用的な扱い:- 以前は
Empty interface{}
という特定の空のインターフェース型が定義されていましたが、これが削除され、Goの組み込み型であるinterface{}
がより汎用的に扱われるようになりました。 Value
インターフェースのInterface()
メソッドの戻り値がEmpty
からinterface{}
に変更されました。NewValue
関数もEmpty
ではなくinterface{}
を引数に取るようになりました。
- 以前は
InterfaceValue.Get()
の追加:reflect.InterfaceValue
型にGet()
メソッドが追加されました。このメソッドは、インターフェースが保持している実際の値(インターフェースの内部にある具体的な値)をinterface{}
型として返します。これにより、リフレクションを通じてインターフェースの内部値にアクセスする際の利便性が向上しました。- アセンブリレベルでのインターフェースサポート:
src/lib/reflect/cast_amd64.s
にAddrToPtrInterface
とPtrInterfaceToAddr
というアセンブリ関数が追加されました。これらは、インターフェースの内部表現(型情報とデータポインタ)とGoのポインタ型との間で変換を行うための低レベルなサポートを提供します。これにより、リフレクションがインターフェースをより効率的に扱えるようになります。
4. strconv
パッケージへの文字列引用符付け機能の追加
strconv
パッケージは、文字列と基本的なデータ型間の変換を提供します。
Quote
関数の追加: 文字列をGoの文字列リテラル形式(ダブルクォートで囲み、特殊文字をエスケープ)に変換するQuote
関数が追加されました。これは、文字列をログに出力したり、設定ファイルに書き込んだりする際に、その内容が曖昧にならないようにするために重要です。CanBackquote
関数の追加: 文字列がバッククォート文字列リテラル(raw string literal、例:`abc`
)として表現可能かどうかを判定するCanBackquote
関数が追加されました。バッククォート文字列は、改行や特殊文字をエスケープせずにそのまま記述できるため、正規表現や複数行の文字列を扱う際に便利です。この関数は、文字列にバッククォート文字()や制御文字が含まれていない場合に
true`を返します。
これらの変更は、Go言語の初期段階における基盤ライブラリの成熟度を高め、開発者がより表現力豊かで堅牢なアプリケーションを構築できるようにするための重要なステップでした。
コアとなるコードの変更箇所
このコミットにおける主要な変更点を、それぞれのパッケージから抜粋して解説します。
src/lib/fmt/format.go
- Fmt
構造体とパディングロジックの変更
--- a/src/lib/fmt/format.go
+++ b/src/lib/fmt/format.go
@@ -39,11 +41,22 @@ export type Fmt struct {
wid_present bool;
prec int;
prec_present bool;
+ // flags
+ minus bool;
+ plus bool;
+ sharp bool;
+ space bool;
+ zero bool;
}
func (f *Fmt) clearflags() {
f.wid_present = false;
f.prec_present = false;
+ f.minus = false;
+ f.plus = false;
+ f.sharp = false;
+ f.space = false;
+ f.zero = false;
}
func (f *Fmt) clearbuf() {
@@ -101,24 +114,28 @@ func (f *Fmt) w(x int) *Fmt {
return f;
}
-// append s to buf, padded on left (w > 0) or right (w < 0)
+// append s to buf, padded on left (w > 0) or right (w < 0 or f.minus)
// padding is in bytes, not characters (agrees with ANSIC C, not Plan 9 C)
func (f *Fmt) pad(s string) {
if f.wid_present && f.wid != 0 {
-\t\tleft := true;
+\t\tleft := !f.minus;
\tw := f.wid;
\tif w < 0 {\
\t\tleft = false;
\t\tw = -w;
\t}\
\tw -= len(s);
+\t\tpadchar := byte(' ');
+\t\tif left && f.zero {
+\t\t\tpadchar = '0';
+\t\t}
\tif w > 0 {\
\t\tif w > NByte {\
\t\t\tw = NByte;
\t\t}\
\t\tbuf := new([]byte, w);
\t\tfor i := 0; i < w; i++ {\
-\t\t\t\tbuf[i] = ' ';
+\t\t\t\tbuf[i] = padchar;
\t\t}\
\t\tif left {\
\t\t\ts = string(buf) + s;
@@ -163,16 +180,35 @@ func (f *Fmt) integer(a int64, base uint, is_signed bool, digits *string) string
if negative {
\ta = -a;
}
-\ti := putint(&buf, NByte-1, uint64(base), uint64(a), digits);\
+\n // two ways to ask for extra leading zero digits: %.3d or %03d.
+\t// apparently the first cancels the second.
+\tprec := 0;
if f.prec_present {
-\t\tfor i > 0 && f.prec > (NByte-1-i) {\
-\t\t\tbuf[i] = '0';
-\t\t\ti--;
+\t\tprec = f.prec;
+\t\tf.zero = false;
+\t} else if f.zero && f.wid_present && !f.minus && f.wid > 0{
+\t\tprec = f.wid;
+\t\tif negative || f.plus || f.space {
+\t\t\tprec--; // leave room for sign
\t}\
}\
+\n i := putint(&buf, NByte-1, uint64(base), uint64(a), digits);
+\tfor i > 0 && prec > (NByte-1-i) {
+\t\tbuf[i] = '0';
+\t\ti--;
+\t}
+\n if negative {
\tbuf[i] = '-';
\ti--;
+\t} else if f.plus {
+\t\tbuf[i] = '+';
+\t\ti--;
+\t} else if f.space {
+\t\tbuf[i] = ' ';
+\t\ti--;
}\
return string(buf)[i+1:NByte];
}
@@ -334,6 +370,44 @@ func (f *Fmt) s(s string) *Fmt {
return f;
}
+// hexadecimal string
+func (f *Fmt) sx(s string) *Fmt {
+\tt := "";
+\tfor i := 0; i < len(s); i++ {
+\t\tv := s[i];
+\t\tt += string(ldigits[v>>4]);
+\t\tt += string(ldigits[v&0xF]);
+\t}
+\tf.pad(t);
+\tf.clearflags();
+\treturn f;
+}
+
+func (f *Fmt) sX(s string) *Fmt {
+\tt := "";
+\tfor i := 0; i < len(s); i++ {
+\t\tv := s[i];
+\t\tt += string(udigits[v>>4]);
+\t\tt += string(udigits[v&0xF]);
+\t}
+\tf.pad(t);
+\tf.clearflags();
+\treturn f;
+}
+
+// quoted string
+func (f *Fmt) q(s string) *Fmt {
+\tvar quoted string;
+\tif f.sharp && strconv.CanBackquote(s) {
+\t\tquoted = "`"+s+"`";
+\t} else {
+\t\tquoted = strconv.Quote(s);
+\t}
+\tf.pad(quoted);
+\tf.clearflags();
+\treturn f;
+}
+
// floating-point
func Prec(f *Fmt, def int) int {
@@ -370,7 +444,7 @@ func (f *Fmt) fb64(a float64) *Fmt {
// cannot defer to float64 versions
// because it will get rounding wrong in corner cases.
func (f *Fmt) e32(a float32) *Fmt {
-\treturn FmtString(f, strconv.ftoa32(a, 'e', Prec(f, -1)));
+\treturn FmtString(f, strconv.ftoa32(a, 'e', Prec(f, 6)));
}
func (f *Fmt) f32(a float32) *Fmt {
解説:
Fmt
構造体にminus
,plus
,sharp
,space
,zero
といったブール型のフラグが追加されました。これらは、printf
スタイルのフォーマット文字列で指定される修飾子(例:%-s
,%+d
,%#x
,% d
,%0d
)に対応します。clearflags()
メソッドもこれらの新しいフラグをリセットするように更新されています。pad
関数は、文字列のパディングロジックを改善しました。特に、f.minus
フラグ(左寄せ)とf.zero
フラグ(ゼロパディング)を考慮するようになりました。padchar
変数が導入され、パディング文字がスペース(デフォルト)または0
(ゼロパディングの場合)に動的に設定されます。integer
関数は、数値のフォーマットにおいて、精度(prec
)とゼロパディング(f.zero
)の相互作用をより正確に処理するようになりました。また、f.plus
(常に符号を表示)とf.space
(正の数値の前にスペース)フラグに基づいて、数値の前に+
またはスペースを追加するロジックが追加されました。sx
,sX
,q
という新しいメソッドがFmt
構造体に追加されました。sx(s string)
: 文字列s
を小文字の16進数表現に変換し、パディングを適用します。sX(s string)
: 文字列s
を大文字の16進数表現に変換し、パディングを適用します。q(s string)
: 文字列s
をGoの引用符付き文字列リテラル形式に変換し、パディングを適用します。f.sharp
フラグが設定されており、文字列がバッククォート可能であれば、バッククォート文字列を使用します。
src/lib/fmt/print.go
- doprintf
と引数処理の変更
--- a/src/lib/fmt/print.go
+++ b/src/lib/fmt/print.go
@@ -186,6 +186,19 @@ export func sprintln(a ...) string {
return s;
}
+
+// Get the i'th arg of the struct value.
+// If the arg itself is an interface, return a value for
+// the thing inside the interface, not the interface itself.
+func getField(v reflect.StructValue, i int) reflect.Value {
+\tval := v.Field(i);
+\tif val.Kind() == reflect.InterfaceKind {
+\t\tinter := val.(reflect.InterfaceValue).Get();
+\t\treturn reflect.NewValue(inter);
+\t}
+\treturn val;
+}
+
// Getters for the fields of the argument structure.
func getBool(v reflect.Value) (val bool, ok bool) {
@@ -227,6 +240,9 @@ func getString(v reflect.Value) (val string, ok bool) {
case reflect.StringKind:
\treturn v.(reflect.StringValue).Get(), true;
}\
+\tif valb, okb := v.Interface().(*[]byte); okb {
+\t\treturn string(valb), true;
+\t}
return "", false;
}
@@ -280,12 +296,6 @@ func parsenum(s string, start, end int) (n int, got bool, newi int) {
if start >= end {
\treturn 0, false, end
}\
-\tif s[start] == '-' {
-\t\ta, b, c := parsenum(s, start+1, end);
-\t\tif b {
-\t\t\treturn -a, b, c;
-\t\t}
-\t}
isnum := false;
num := 0;
for '0' <= s[start] && s[start] <= '9' {
@@ -371,10 +381,28 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
\t\ti += w;
\t\tcontinue;
\t}\
-\t\t// saw % - do we have %20 (width)?
-\t\tp.wid, p.wid_ok, i = parsenum(format, i+1, end);\
+\t\ti++;
+\t\t// flags
+\t\tF: for ; i < end; i++ {
+\t\t\tswitch format[i] {
+\t\t\tcase '#':
+\t\t\t\tp.fmt.sharp = true;
+\t\t\tcase '0':
+\t\t\t\tp.fmt.zero = true;
+\t\t\tcase '+':
+\t\t\t\tp.fmt.plus = true;
+\t\t\tcase '-':
+\t\t\t\tp.fmt.minus = true;
+\t\t\tcase ' ':
+\t\t\t\tp.fmt.space = true;
+\t\t\tdefault:
+\t\t\t\tbreak F;
+\t\t\t}
+\t\t}
+\t\t// do we have 20 (width)?
+\t\tp.wid, p.wid_ok, i = parsenum(format, i, end);
\tp.prec_ok = false;
-\t\t// do we have %.20 (precision)?
+\t\t// do we have .20 (precision)?
\tif i < end && format[i] == '.' {
\t\tp.prec, p.prec_ok, i = parsenum(format, i+1, end);
\t}
@@ -391,7 +419,7 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
\t\tp.addstr("(missing)");
\t\tcontinue;
\t}\
-\t\tfield := v.Field(fieldnum);\
+\t\tfield := getField(v, fieldnum);
\tfieldnum++;
\tif c != 'T' {\t// don't want thing to describe itself if we're asking for its type
\t\tif formatter, ok := field.Interface().(Format); ok {
@@ -463,6 +491,20 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
\t\t\t\t\t} else {
\t\t\t\t\t\ts = p.fmt.ux64(uint64(v)).str()
\t\t\t\t\t}\
+\t\t\t\t} else if v, ok := getString(field); ok {
+\t\t\t\t\ts = p.fmt.sx(v).str();
+\t\t\t\t} else {
+\t\t\t\t\tgoto badtype
+\t\t\t\t}
+\t\t\tcase 'X':
+\t\t\t\tif v, signed, ok := getInt(field); ok {
+\t\t\t\t\tif signed {
+\t\t\t\t\t\ts = p.fmt.X64(v).str()
+\t\t\t\t\t} else {
+\t\t\t\t\t\ts = p.fmt.uX64(uint64(v)).str()
+\t\t\t\t\t}
+\t\t\t\t} else if v, ok := getString(field); ok {
+\t\t\t\t\ts = p.fmt.sX(v).str();
\t\t\t\t} else {
\t\t\t\t\tgoto badtype
\t\t\t\t}
@@ -500,6 +542,12 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
\t\t\t\t} else {
\t\t\t\t\tgoto badtype
\t\t\t\t}\
+\t\t\tcase 'q':
+\t\t\t\tif v, ok := getString(field); ok {
+\t\t\t\t\ts = p.fmt.q(v).str()
+\t\t\t\t} else {
+\t\t\t\t\tgoto badtype
+\t\t\t\t}
\t\t// pointer
\t\tcase 'p':
@@ -530,7 +578,7 @@ func (p *P) doprintf(format string, v reflect.StructValue) {
\tif fieldnum < v.Len() {
\t\tp.addstr("?(extra ");
\t\tfor ; fieldnum < v.Len(); fieldnum++ {
-\t\t\tp.addstr(v.Field(fieldnum).Type().String());
+\t\t\tp.addstr(getField(v, fieldnum).Type().String());
\t\t\tif fieldnum + 1 < v.Len() {
\t\t\t\tp.addstr(", ");
\t\t\t}\
@@ -543,7 +591,7 @@ func (p *P) doprint(v reflect.StructValue, addspace, addnewline bool) {
\tprev_string := false;
\tfor fieldnum := 0; fieldnum < v.Len(); fieldnum++ {
\t\t// always add spaces if we're doing println
-\t\tfield := v.Field(fieldnum);\
+\t\tfield := getField(v, fieldnum);
\t\tif fieldnum > 0 {
\t\t\tif addspace {
\t\t\t\tp.add(' ')
解説:
getField
というヘルパー関数が追加されました。この関数は、reflect.StructValue
から指定されたインデックスのフィールドを取得します。もしそのフィールドがインターフェース型であれば、そのインターフェースが保持している実際の値(reflect.InterfaceValue.Get()
で取得)をreflect.NewValue
でラップして返します。これにより、fmt
パッケージがインターフェースの内部の具体的な値を直接フォーマットできるようになり、より柔軟な出力が可能になります。getString
関数は、reflect.Value
が*[]byte
型である場合にもstring
に変換できるように拡張されました。これは、バイトスライスを文字列としてフォーマットする際の利便性を高めます。doprintf
関数は、フォーマット文字列のパースロジックが大幅に改善されました。%
の直後に続くフラグ(#
,0
,+
,-
,p.fmt
(Fmt
構造体)の対応するフラグを設定するループが追加されました。- 幅(width)と精度(precision)のパースロジックも調整され、フラグの後に続く数値として正しく解釈されるようになりました。
switch c
文に新しいケース'X'
,'x'
,'q'
が追加され、それぞれp.fmt.sX
,p.fmt.sx
,p.fmt.q
メソッドを呼び出すことで、16進数文字列フォーマットと引用符付き文字列フォーマットをサポートします。
doprintf
とdoprint
関数内で、引数のフィールドを取得する際に直接v.Field(fieldnum)
を呼び出す代わりに、新しく追加されたgetField
ヘルパー関数を使用するように変更されました。これにより、インターフェースの内部値が適切に処理されるようになります。
src/lib/strconv/quote.go
- 新しい文字列引用符付け機能
--- /dev/null
+++ b/src/lib/strconv/quote.go
@@ -0,0 +1,76 @@
+// 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 strconv
+
+import (
+\t"utf8";
+)
+
+const ldigits = "0123456789abcdef"
+const udigits = "0123456789ABCDEF"
+
+export func Quote(s string) string {
+\tt := "`";
+\tfor i := 0; i < len(s); i++ {
+\t\tswitch {
+\t\tcase s[i] == '"':
+\t\t\tt += `\"`;
+\t\tcase s[i] == '\\':
+\t\t\tt += `\\`;
+\t\tcase ' ' <= s[i] && s[i] <= '~':
+\t\t\tt += string(s[i]);
+\t\tcase s[i] == '\a':
+\t\t\tt += `\a`;
+\t\tcase s[i] == '\b':
+\t\t\tt += `\b`;
+\t\tcase s[i] == '\f':
+\t\t\tt += `\f`;
+\t\tcase s[i] == '\n':
+\t\t\tt += `\n`;
+\t\tcase s[i] == '\r':
+\t\t\tt += `\r`;
+\t\tcase s[i] == '\t':
+\t\t\tt += `\t`;
+\t\tcase s[i] == '\v':
+\t\t\tt += `\v`;
+
+\t\tcase utf8.FullRuneInString(s, i):
+\t\t\tr, size := utf8.DecodeRuneInString(s, i);
+\t\t\tif r == utf8.RuneError && size == 1 {
+\t\t\t\tgoto EscX;
+\t\t\t}
+\t\t\ti += size-1; // i++ on next iteration
+\t\t\tif r < 0x10000 {
+\t\t\t\tt += `\u`;
+\t\t\t\tfor j:=uint(0); j<4; j++ {
+\t\t\t\t\tt += string(ldigits[(r>>(12-4*j))&0xF]);
+\t\t\t\t}
+\t\t\t} else {
+\t\t\t\tt += `\U`;
+\t\t\t\tfor j:=uint(0); j<8; j++ {
+\t\t\t\t\tt += string(ldigits[(r>>(28-4*j))&0xF]);
+\t\t\t\t}\
+\t\t\t}
+
+\t\tdefault:
+\t\tEscX:
+\t\t\tt += `\x`;
+\t\t\tt += string(ldigits[s[i]>>4]);
+\t\t\tt += string(ldigits[s[i]&0xF]);
+\t\t}
+\t}
+\tt += `"`;
+\treturn t;
+}
+
+export func CanBackquote(s string) bool {
+\tfor i := 0; i < len(s); i++ {
+\t\tif s[i] < ' ' || s[i] == '`' {
+\t\t\treturn false;
+\t\t}
+\t}
+\treturn true;
+}
解説:
Quote(s string) string
関数は、入力文字列s
をGoのダブルクォート文字列リテラル形式に変換して返します。- ダブルクォート(
"
)とバックスラッシュ(\
)はそれぞれ\"
と\\
にエスケープされます。 - 表示可能なASCII文字(スペースからチルダまで)はそのまま追加されます。
- 一般的なエスケープシーケンス(
\a
,\b
,\f
,\n
,\r
,\t
,\v
)に対応します。 - UTF-8でエンコードされたUnicode文字は、
\uXXXX
(U+FFFFまで)または\UXXXXXXXX
(U+10FFFFまで)の形式でエスケープされます。 - その他の制御文字や不正なUTF-8シーケンスは
\xXX
の形式で16進数エスケープされます。
- ダブルクォート(
CanBackquote(s string) bool
関数は、入力文字列s
がGoのバッククォート文字列リテラル(raw string literal)として表現可能かどうかを判定します。- バッククォート文字列は、改行や特殊文字をエスケープせずにそのまま記述できるため、文字列内にバッククォート文字(
)や制御文字(ASCIIコード32未満の文字)が含まれていない場合にのみ使用できます。この関数は、これらの条件を満たす場合に
true`を返します。
- バッククォート文字列は、改行や特殊文字をエスケープせずにそのまま記述できるため、文字列内にバッククォート文字(
src/lib/utf8.go
- 文字列からのルーンデコード機能
--- a/src/lib/utf8.go
+++ b/src/lib/utf8.go
@@ -9,7 +9,8 @@ package utf8
export const (
\tRuneError = 0xFFFD;
\tRuneSelf = 0x80;
-\tRuneMax = 1<<21 - 1;
+\tRuneMax = 0x10FFFF;
+\tUTFMax = 4;
)
const (
@@ -105,17 +107,103 @@ func DecodeRuneInternal(p *[]byte) (rune, size int, short bool) {
return RuneError, 1, false
}
+func DecodeRuneInStringInternal(s string, i int) (rune, size int, short bool) {
+\tn := len(s) - i;
+\tif n < 1 {
+\t\treturn RuneError, 0, true;
+\t}
+\tc0 := s[i];
+
+\t// 1-byte, 7-bit sequence?
+\tif c0 < Tx {
+\t\treturn int(c0), 1, false
+\t}
+
+\t// unexpected continuation byte?
+\tif c0 < T2 {
+\t\treturn RuneError, 1, false
+\t}
+
+\t// need first continuation byte
+\tif n < 2 {
+\t\treturn RuneError, 1, true
+\t}
+\tc1 := s[i+1];
+\tif c1 < Tx || T2 <= c1 {
+\t\treturn RuneError, 1, false
+\t}
+
+\t// 2-byte, 11-bit sequence?
+\tif c0 < T3 {
+\t\trune = int(c0&Mask2)<<6 | int(c1&Maskx);
+\t\tif rune <= Rune1Max {
+\t\t\treturn RuneError, 1, false
+\t\t}
+\t\treturn rune, 2, false
+\t}
+
+\t// need second continuation byte
+\tif n < 3 {
+\t\treturn RuneError, 1, true
+\t}
+\tc2 := s[i+2];
+\tif c2 < Tx || T2 <= c2 {
+\t\treturn RuneError, 1, false
+\t}
+
+\t// 3-byte, 16-bit sequence?
+\tif c0 < T4 {
+\t\trune = int(c0&Mask3)<<12 | int(c1&Maskx)<<6 | int(c2&Maskx);
+\t\tif rune <= Rune2Max {
+\t\t\treturn RuneError, 1, false
+\t\t}
+\t\treturn rune, 3, false
+\t}
+
+\t// need third continuation byte
+\tif n < 4 {
+\t\treturn RuneError, 1, true
+\t}
+\tc3 := s[i+3];
+\tif c3 < Tx || T2 <= c3 {
+\t\treturn RuneError, 1, false
+\t}
+
+\t// 4-byte, 21-bit sequence?
+\tif c0 < T5 {
+\t\trune = int(c0&Mask4)<<18 | int(c1&Maskx)<<12 | int(c2&Maskx)<<6 | int(c3&Maskx);
+\t\tif rune <= Rune3Max {
+\t\t\treturn RuneError, 1, false
+\t\t}
+\t\treturn rune, 4, false
+\t}
+
+\t// error
+\treturn RuneError, 1, false
+}
+
export func FullRune(p *[]byte) bool {
\trune, size, short := DecodeRuneInternal(p);
\treturn !short
}
+export func FullRuneInString(s string, i int) bool {
+\trune, size, short := DecodeRuneInStringInternal(s, i);
+\treturn !short
+}
+
export func DecodeRune(p *[]byte) (rune, size int) {
\tvar short bool;
\trune, size, short = DecodeRuneInternal(p);
\treturn;
}
+export func DecodeRuneInString(s string, i int) (rune, size int) {
+\tvar short bool;
+\trune, size, short = DecodeRuneInStringInternal(s, i);
+\treturn;
+}
+
export func RuneLen(rune int) int {
\tswitch {
\tcase rune <= Rune1Max:
解説:
RuneMax
とUTFMax
の定数が更新され、Unicodeの最大コードポイントとUTF-8の最大バイト長を正確に反映するようになりました。DecodeRuneInStringInternal(s string, i int)
関数が追加されました。これは、[]byte
ではなくstring
型のs
のi
番目のインデックスからUTF-8ルーンをデコードするための内部ヘルパー関数です。この関数は、UTF-8のバイトシーケンスを解析し、対応するルーンとバイト長を返します。不正なシーケンスや不完全なシーケンスの場合にはRuneError
を返します。FullRuneInString(s string, i int) bool
関数が追加されました。これは、s
のi
番目のインデックスから始まるバイトシーケンスが完全なUTF-8ルーンを形成しているかどうかを判定します。DecodeRuneInString(s string, i int) (rune, size int)
関数が追加されました。これは、s
のi
番目のインデックスからUTF-8ルーンをデコードし、ルーンとバイト長を返します。
src/lib/reflect/value.go
- インターフェースの扱い
--- a/src/lib/reflect/value.go
+++ b/src/lib/reflect/value.go
@@ -36,14 +36,13 @@ func AddrToPtrString(Addr) *string
func AddrToPtrBool(Addr) *bool
func AddrToPtrRuntimeArray(Addr) *RuntimeArray
func PtrRuntimeArrayToAddr(*RuntimeArray) Addr
-\n-export type Empty interface {}\t// TODO(r): Delete when no longer needed?
+func AddrToPtrInterface(Addr) *interface{}
export type Value interface {
\tKind()\tint;
\tType()\tType;
\tAddr()\tAddr;
-\tInterface()\tEmpty;
+\tInterface()\tinterface {};
}
// Common fields and functionality for all values
@@ -66,7 +65,7 @@ func (c *Common) Addr() Addr {
\treturn c.addr
}
-func (c *Common) Interface() Empty {
+func (c *Common) Interface() interface {} {
\treturn sys.unreflect(*AddrToPtrAddr(c.addr), c.typ.String());
}
@@ -714,12 +713,17 @@ func StructCreator(typ Type, addr Addr) Value {
export type InterfaceValue interface {
\tKind()\tint;
\tType()\tType;
+\tGet()\tinterface {};
}
type InterfaceValueStruct struct {
\tCommon
}
+func (v *InterfaceValueStruct) Get() interface{} {
+\treturn *AddrToPtrInterface(v.addr);
+}
+
func InterfaceCreator(typ Type, addr Addr) Value {
\treturn &InterfaceValueStruct{ Common{InterfaceKind, typ, addr} }\
}
@@ -824,7 +828,7 @@ export func NewOpenArrayValue(typ ArrayType, len, cap int) ArrayValue {
\treturn NewValueAddr(typ, PtrRuntimeArrayToAddr(array));
}
-export func NewValue(e Empty) Value {
+export func NewValue(e interface {}) Value {
\tvalue, typestring := sys.reflect(e);\
\tp, ok := typecache[typestring];
\tif !ok {
解説:
Empty interface{}
型が削除され、Goの組み込みの空のインターフェース型interface{}
が使用されるようになりました。これにより、リフレクションAPIがより標準的で汎用的なインターフェースの扱いをサポートします。Value
インターフェースのInterface()
メソッドの戻り値がEmpty
からinterface{}
に変更されました。NewValue
関数もEmpty
ではなくinterface{}
を引数に取るようになりました。InterfaceValue
インターフェースにGet()
メソッドが追加されました。このメソッドは、InterfaceValueStruct
の実装で、インターフェースが保持する実際の値を*AddrToPtrInterface(v.addr)
を通じて取得し、interface{}
型として返します。これは、リフレクションを通じてインターフェースの内部値にアクセスする際の重要な変更です。AddrToPtrInterface(Addr) *interface{}
という関数宣言が追加されました。これは、アセンブリコードで実装される、アドレスからインターフェースポインタへの変換関数です。
関連リンク
- Go言語公式ドキュメント:
fmt
パッケージ: https://pkg.go.dev/fmtreflect
パッケージ: https://pkg.go.dev/reflectstrconv
パッケージ: https://pkg.go.dev/strconvutf8
パッケージ: https://pkg.go.dev/unicode/utf8bufio
パッケージ: https://pkg.go.dev/bufio
- Go言語の文字列、バイト、ルーンについて: https://go.dev/blog/strings
- Go言語のインターフェースについて: https://go.dev/blog/interfaces
参考にした情報源リンク
- Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語の設計に関する議論(Go Mailing Listなど、当時の情報源)
- Unicode標準: https://www.unicode.org/
- UTF-8エンコーディングの仕様: https://www.rfc-editor.org/rfc/rfc3629
- Plan 9アセンブリ(Goのアセンブリの基盤)に関する情報