[インデックス 1017] ファイルの概要
このコミットは、Go言語の初期のpretty
パッケージにおける、構造体フィールドタグの処理に関する改善を目的としています。具体的には、パーサーとプリティプリンターがフィールドタグを正しく認識し、整形できるようにするための変更が含まれています。
コミット
- コミットハッシュ:
42ae5270d8c1680bac1921725a83a92969b24f7d
- 作者: Robert Griesemer gri@golang.org
- コミット日時: 2008年10月31日 金曜日 14:27:34 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/42ae5270d8c1680bac1921725a83a92969b24f7d
元コミット内容
- handle field tags in pretty printer
R=r
OCL=18264
CL=18264
変更の背景
このコミットが行われた2008年10月は、Go言語がまだ一般に公開される前の非常に初期の段階でした。Go言語の設計思想の一つに、シンプルさと実用性があります。構造体のフィールドタグは、Go言語が提供する強力なメタデータ機能の一つであり、リフレクションと組み合わせて、JSONエンコーディング/デコーディング、データベースマッピング、コマンドライン引数解析など、様々な用途で利用されます。
pretty
パッケージ(当時の名称であり、現在のgo/printer
パッケージの前身にあたるものと推測されます)は、Goのソースコードを整形(プリティプリント)するためのツールであったと考えられます。初期の実装では、構造体のフィールドに付与されるタグが正しく扱われていなかったため、整形後のコードが元の意図と異なる、あるいはコンパイルエラーを引き起こす可能性がありました。このコミットは、この問題を解決し、フィールドタグを含むGoコードの正確な整形を保証するために導入されました。
前提知識の解説
Go言語の構造体フィールドタグ
Go言語において、構造体(struct
)のフィールドには、追加のメタデータとして「タグ(tag)」を付与することができます。タグは文字列リテラルとしてフィールド宣言の直後に記述され、通常はバッククォート(`)で囲まれます。
例:
type User struct {
Name string `json:"user_name" db:"name"`
Age int `json:"age"`
}
上記の例では、Name
フィールドにjson:"user_name" db:"name"
というタグが付与されています。これは、このフィールドがJSONにエンコードされる際にはuser_name
というキーで、データベースにマッピングされる際にはname
というカラム名で扱われるべきであることを示唆しています。
タグは、Goのリフレクション機能(reflect
パッケージ)を通じて実行時に読み取ることができ、これにより汎用的なデータ処理ライブラリやツールを記述することが可能になります。
Go言語のパーサーとプリティプリンター
Go言語のツールチェインには、ソースコードを解析(パース)し、抽象構文木(AST: Abstract Syntax Tree)を構築するパーサーと、ASTを元にソースコードを整形して出力するプリティプリンターが含まれています。
- パーサー: ソースコードのテキストを読み込み、Go言語の文法規則に従って、プログラムの構造を表現するASTを生成します。このASTは、コンパイラ、リンカ、各種ツール(
go fmt
など)によって利用されます。 - プリティプリンター: ASTを受け取り、Go言語の標準的なフォーマット規則(
go fmt
が適用する規則)に従って、整形されたソースコードを出力します。これにより、Goコードの可読性と一貫性が保たれます。
このコミットは、パーサーがフィールドタグをASTに正しく取り込み、プリティプリンターがそのASTからフィールドタグを正しく出力できるようにするための修正を含んでいます。
技術的詳細
このコミットの主要な変更点は、usr/gri/pretty/parser.go
とusr/gri/pretty/printer.go
に集中しています。
parser.go
の変更点
parser.go
では、構造体型(ParseStructType
関数)の解析ロジックが修正されています。以前は、構造体フィールドの宣言リストを解析する際に、フィールドタグ(文字列リテラルとして表現される)を適切に処理していませんでした。
変更前は、構造体フィールドの宣言の後にScanner.STRING
(文字列リテラル)が来た場合に、それをフィールドタグとして認識するロジックが不足していました。変更後は、ParseOperand()
を呼び出すことで文字列リテラル(フィールドタグ)を解析し、それを構造体フィールドのリストに追加するように修正されています。これにより、パーサーはフィールドタグをASTの一部として正しく取り込むことができるようになりました。
また、ParseVarDeclList
関数におけるellipsis_ok
パラメータの扱いがより厳密になり、パラメータリストのコンテキストでのみ特定のチェックが適用されるようになりました。これは、構造体フィールド宣言と関数パラメータ宣言の文法的な違いをより正確に反映するための変更です。
printer.go
の変更点
printer.go
では、構造体フィールドのリストを整形するFields
関数が修正されています。この関数は、ASTからフィールド情報を読み取り、整形されたコードとして出力します。
変更前は、前のトークンがScanner.TYPE
であった場合にのみ改行とセミコロンを挿入するロジックでしたが、フィールドタグ(Scanner.STRING
)が導入されたことで、そのロジックを調整する必要がありました。
変更後:
if prev == Scanner.TYPE && x.tok != Scanner.STRING || prev == Scanner.STRING {
P.semi, P.newl = true, 1;
} else if prev == x.tok {
P.String(0, ", ");
}
この変更により、前の要素が型であり、かつ現在の要素が文字列(フィールドタグ)でない場合、または前の要素が文字列(フィールドタグ)である場合に、改行とセミコロンを挿入するようになりました。これにより、フィールドタグが正しく整形され、構造体フィールド宣言が適切なフォーマットで出力されるようになります。
その他の変更点
usr/gri/pretty/selftest0.go
が新規追加されました。このファイルは、フィールドタグを含む構造体の定義を含むテストケースであり、今回の変更が正しく機能することを確認するために使用されます。usr/gri/pretty/selftest.go
がselftest1.go
にリネームされました。usr/gri/pretty/test.sh
が更新され、新しいselftest0.go
のテスト実行と、selftest.go
からselftest1.go
へのリネームに対応しました。
コアとなるコードの変更箇所
usr/gri/pretty/parser.go
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -326,7 +326,7 @@ func (P *Parser) ParseVarDeclList(list *AST.List, ellipsis_ok bool) {
// parse a list of types
i0 := list.len();
for {
- list.Add(P.ParseVarDecl(i0 > 0));
+ list.Add(P.ParseVarDecl(ellipsis_ok /* param list */ && i0 > 0));
if P.tok == Scanner.COMMA {
P.Next();
} else {
@@ -340,7 +340,7 @@ func (P *Parser) ParseVarDeclList(list *AST.List, ellipsis_ok bool) {
P.Next();
}
- if i0 > 0 && typ == nil {
+ if ellipsis_ok /* param list */ && i0 > 0 && typ == nil {
// not the first parameter section; we must have a type
P.Error(P.pos, "type expected");
typ = AST.BadType;
@@ -365,18 +365,10 @@ func (P *Parser) ParseVarDeclList(list *AST.List, ellipsis_ok bool) {
} else {
// all list entries are types
// convert all type entries into type expressions
- if i0 > 0 {
- panic("internal parser error");
- }
-
for i, n := 0, list.len(); i < n; i++ {
t := list.at(i).(*AST.Type);
list.set(i, AST.NewTypeExpr(t));
}
-
- if P.tok == Scanner.COMMA {
- panic("internal parser error");
- }
}
P.Ecart();
@@ -514,6 +506,8 @@ func (P *Parser) ParseMapType() *AST.Type {\n }\n \n \n+func (P *Parser) ParseOperand() *AST.Expr\n+\n func (P *Parser) ParseStructType() *AST.Type {\n P.Trace("StructType");
\n @@ -522,10 +516,16 @@ func (P *Parser) ParseStructType() *AST.Type {\n if P.tok == Scanner.LBRACE {\n P.Next();\n t.list = AST.NewList();\n- for P.tok == Scanner.IDENT {\n+ for P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {\n P.ParseVarDeclList(t.list, false);\n- if P.tok != Scanner.RBRACE {\n- P.Expect(Scanner.SEMICOLON);\n+ if P.tok == Scanner.STRING {\n+ // ParseOperand takes care of string concatenation
+ t.list.Add(P.ParseOperand());\n+ }\n+ if P.tok == Scanner.SEMICOLON {\n+ P.Next();\n+ } else {\n+ break;\n }\n }\n \tP.OptSemicolon();
usr/gri/pretty/printer.go
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -139,7 +139,7 @@ func (P *Printer) Fields(list *AST.List) {\n for i, n := 0, list.len(); i < n; i++ {\n x := list.at(i).(*AST.Expr);\n if i > 0 {\n-\t\t\tif prev == Scanner.TYPE {\n+\t\t\tif prev == Scanner.TYPE && x.tok != Scanner.STRING || prev == Scanner.STRING {\n \t\t\t\tP.semi, P.newl = true, 1;\n \t\t\t} else if prev == x.tok {\n \t\t\t\tP.String(0, ", ");
usr/gri/pretty/selftest0.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 main
type Proto struct {
a int "a tag";
b, c, d *Proto "bcd" "tag";
*Proto "proto tag"
}
コアとなるコードの解説
parser.go
の変更解説
-
ParseVarDeclList
のellipsis_ok
の修正:- 変更前:
list.Add(P.ParseVarDecl(i0 > 0));
- 変更後:
list.Add(P.ParseVarDecl(ellipsis_ok /* param list */ && i0 > 0));
- この変更は、
ParseVarDecl
に渡される引数が、関数パラメータリストのコンテキストでのみellipsis_ok
(可変長引数を示す...
の許可)を考慮するように修正しています。これにより、構造体フィールド宣言の解析時に不必要なロジックが適用されるのを防ぎ、パーサーの正確性を向上させています。 - 同様に、
if i0 > 0 && typ == nil {
の条件もif ellipsis_ok /* param list */ && i0 > 0 && typ == nil {
に変更され、型が必須であるというチェックがパラメータリストのコンテキストに限定されました。 - また、
if i0 > 0 { panic("internal parser error"); }
やif P.tok == Scanner.COMMA { panic("internal parser error"); }
といった、型リストの処理における不要なpanic
が削除されました。これは、フィールドタグの導入により、型リストの解析ロジックがより柔軟になったため、これらの厳しすぎるチェックが不要になったことを示唆しています。
- 変更前:
-
ParseStructType
におけるフィールドタグの解析:- 変更前は、
for P.tok == Scanner.IDENT { ... }
のループで識別子(フィールド名)のみを期待していました。 - 変更後:
for P.tok != Scanner.RBRACE && P.tok != Scanner.EOF { ... }
のループに変更され、構造体の閉じブレース}
またはファイルの終端EOF
まで解析を続けるようになりました。 - 最も重要な変更は、このループ内に
if P.tok == Scanner.STRING { ... }
というブロックが追加されたことです。t.list.Add(P.ParseOperand());
が追加され、現在のトークンが文字列リテラル(Scanner.STRING
)である場合、それをParseOperand()
で解析し、構造体フィールドのリスト(t.list
)に追加するようにしました。ParseOperand()
は文字列リテラルの連結なども処理できるため、複数の文字列リテラルがタグとして連続して記述された場合にも対応できます。- これにより、パーサーは構造体フィールド宣言の後に続く文字列リテラルをフィールドタグとして正しく認識し、ASTに組み込むことができるようになりました。
- セミコロンの処理も調整され、フィールドタグの後にセミコロンが続く場合も正しく処理されるようになりました。
- 変更前は、
printer.go
の変更解説
Fields
関数における整形ロジックの修正:- 変更前:
if prev == Scanner.TYPE { P.semi, P.newl = true, 1; }
- 変更後:
if prev == Scanner.TYPE && x.tok != Scanner.STRING || prev == Scanner.STRING { P.semi, P.newl = true, 1; }
- この変更は、構造体フィールドの整形ルールを更新しています。
prev == Scanner.TYPE && x.tok != Scanner.STRING
: 前の要素が型であり、かつ現在の要素が文字列(フィールドタグ)ではない場合、改行とセミコロンを挿入します。これは通常のフィールド宣言(Name string
)の後に適用されます。prev == Scanner.STRING
: 前の要素が文字列(フィールドタグ)である場合、改行とセミコロンを挿入します。これは、フィールドタグの後に次のフィールド宣言が続く場合に適用され、タグが独立した要素として扱われることを保証します。
- このロジックにより、フィールドタグが構造体フィールド宣言の一部として正しく整形され、可読性の高い出力が生成されるようになります。
- 変更前:
selftest0.go
の追加解説
この新しいテストファイルは、以下のような構造体定義を含んでいます。
type Proto struct {
a int "a tag";
b, c, d *Proto "bcd" "tag";
*Proto "proto tag"
}
この定義は、Go言語のフィールドタグの様々な使用例を網羅しています。
a int "a tag";
: 単一のフィールドと単一のタグ。b, c, d *Proto "bcd" "tag";
: 複数のフィールドと複数のタグ。Goでは、複数の文字列リテラルを連続して記述することで、それらが連結されて一つのタグ文字列となるため、これは"bcdtag"
というタグになります。*Proto "proto tag"
: 埋め込みフィールド(匿名フィールド)とタグ。
このテストケースの追加により、パーサーとプリティプリンターがこれらの複雑なフィールドタグのシナリオを正しく処理できることが検証されます。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の仕様(Struct typesのセクション): https://go.dev/ref/spec#Struct_types
- Go言語のリフレクション(
reflect
パッケージ): https://pkg.go.dev/reflect
参考にした情報源リンク
- Go言語の公式ドキュメントおよび仕様書
- Go言語のソースコード(特に
go/parser
およびgo/printer
パッケージの歴史的なコミット) - Go言語の構造体タグに関する一般的な解説記事(Web検索を通じて得られた情報)
注記: このコミットはGo言語の非常に初期の段階のものであり、当時のusr/gri/pretty
パッケージは現在のgo/parser
やgo/printer
パッケージの直接の前身にあたるものと推測されます。現在のGo言語のパーサーやプリンターは、より洗練された実装になっていますが、このコミットはフィールドタグのサポートがどのように導入されたかを示す貴重な歴史的記録です。# [インデックス 1017] ファイルの概要
このコミットは、Go言語の初期のpretty
パッケージにおける、構造体フィールドタグの処理に関する改善を目的としています。具体的には、パーサーとプリティプリンターがフィールドタグを正しく認識し、整形できるようにするための変更が含まれています。
コミット
- コミットハッシュ:
42ae5270d8c1680bac1921725a83a92969b24f7d
- 作者: Robert Griesemer gri@golang.org
- コミット日時: 2008年10月31日 金曜日 14:27:34 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/42ae5270d8c1680bac1921725a83a92969b24f7d
元コミット内容
- handle field tags in pretty printer
R=r
OCL=18264
CL=18264
変更の背景
このコミットが行われた2008年10月は、Go言語がまだ一般に公開される前の非常に初期の段階でした。Go言語の設計思想の一つに、シンプルさと実用性があります。構造体(struct
)のフィールドタグは、Go言語が提供する強力なメタデータ機能の一つであり、リフレクションと組み合わせて、JSONエンコーディング/デコーディング、データベースマッピング、コマンドライン引数解析など、様々な用途で利用されます。
pretty
パッケージ(当時の名称であり、現在のgo/printer
パッケージの前身にあたるものと推測されます)は、Goのソースコードを整形(プリティプリント)するためのツールであったと考えられます。初期の実装では、構造体のフィールドに付与されるタグが正しく扱われていなかったため、整形後のコードが元の意図と異なる、あるいはコンパイルエラーを引き起こす可能性がありました。このコミットは、この問題を解決し、フィールドタグを含むGoコードの正確な整形を保証するために導入されました。
前提知識の解説
Go言語の構造体フィールドタグ
Go言語において、構造体(struct
)のフィールドには、追加のメタデータとして「タグ(tag)」を付与することができます。タグは文字列リテラルとしてフィールド宣言の直後に記述され、通常はバッククォート(`)で囲まれます。
例:
type User struct {
Name string `json:"user_name" db:"name"`
Age int `json:"age"`
}
上記の例では、Name
フィールドにjson:"user_name" db:"name"
というタグが付与されています。これは、このフィールドがJSONにエンコードされる際にはuser_name
というキーで、データベースにマッピングされる際にはname
というカラム名で扱われるべきであることを示唆しています。
タグは、Goのリフレクション機能(reflect
パッケージ)を通じて実行時に読み取ることができ、これにより汎用的なデータ処理ライブラリやツールを記述することが可能になります。
Go言語のパーサーとプリティプリンター
Go言語のツールチェインには、ソースコードを解析(パース)し、抽象構文木(AST: Abstract Syntax Tree)を構築するパーサーと、ASTを元にソースコードを整形して出力するプリティプリンターが含まれています。
- パーサー: ソースコードのテキストを読み込み、Go言語の文法規則に従って、プログラムの構造を表現するASTを生成します。このASTは、コンパイラ、リンカ、各種ツール(
go fmt
など)によって利用されます。 - プリティプリンター: ASTを受け取り、Go言語の標準的なフォーマット規則(
go fmt
が適用する規則)に従って、整形されたソースコードを出力します。これにより、Goコードの可読性と一貫性が保たれます。
このコミットは、パーサーがフィールドタグをASTに正しく取り込み、プリティプリンターがそのASTからフィールドタグを正しく出力できるようにするための修正を含んでいます。
技術的詳細
このコミットの主要な変更点は、usr/gri/pretty/parser.go
とusr/gri/pretty/printer.go
に集中しています。
parser.go
の変更点
parser.go
では、構造体型(ParseStructType
関数)の解析ロジックが修正されています。以前は、構造体フィールドの宣言リストを解析する際に、フィールドタグ(文字列リテラルとして表現される)を適切に処理していませんでした。
変更前は、構造体フィールドの宣言の後にScanner.STRING
(文字列リテラル)が来た場合に、それをフィールドタグとして認識するロジックが不足していました。変更後は、ParseOperand()
を呼び出すことで文字列リテラル(フィールドタグ)を解析し、それを構造体フィールドのリストに追加するように修正されています。これにより、パーサーはフィールドタグをASTの一部として正しく取り込むことができるようになりました。
また、ParseVarDeclList
関数におけるellipsis_ok
パラメータの扱いがより厳密になり、パラメータリストのコンテキストでのみ特定のチェックが適用されるようになりました。これは、構造体フィールド宣言と関数パラメータ宣言の文法的な違いをより正確に反映するための変更です。
printer.go
の変更点
printer.go
では、構造体フィールドのリストを整形するFields
関数が修正されています。この関数は、ASTからフィールド情報を読み取り、整形されたコードとして出力します。
変更前は、前のトークンがScanner.TYPE
であった場合にのみ改行とセミコロンを挿入するロジックでしたが、フィールドタグ(Scanner.STRING
)が導入されたことで、そのロジックを調整する必要がありました。
変更後:
if prev == Scanner.TYPE && x.tok != Scanner.STRING || prev == Scanner.STRING {
P.semi, P.newl = true, 1;
} else if prev == x.tok {
P.String(0, ", ");
}
この変更により、前の要素が型であり、かつ現在の要素が文字列(フィールドタグ)でない場合、または前の要素が文字列(フィールドタグ)である場合に、改行とセミコロンを挿入するようになりました。これにより、フィールドタグが正しく整形され、構造体フィールド宣言が適切なフォーマットで出力されるようになります。
その他の変更点
usr/gri/pretty/selftest0.go
が新規追加されました。このファイルは、フィールドタグを含む構造体の定義を含むテストケースであり、今回の変更が正しく機能することを確認するために使用されます。usr/gri/pretty/selftest.go
がselftest1.go
にリネームされました。usr/gri/pretty/test.sh
が更新され、新しいselftest0.go
のテスト実行と、selftest.go
からselftest1.go
へのリネームに対応しました。
コアとなるコードの変更箇所
usr/gri/pretty/parser.go
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -326,7 +326,7 @@ func (P *Parser) ParseVarDeclList(list *AST.List, ellipsis_ok bool) {
// parse a list of types
i0 := list.len();
for {
- list.Add(P.ParseVarDecl(i0 > 0));
+ list.Add(P.ParseVarDecl(ellipsis_ok /* param list */ && i0 > 0));
if P.tok == Scanner.COMMA {
P.Next();
} else {
@@ -340,7 +340,7 @@ func (P *Parser) ParseVarDeclList(list *AST.List, ellipsis_ok bool) {
P.Next();
}
- if i0 > 0 && typ == nil {
+ if ellipsis_ok /* param list */ && i0 > 0 && typ == nil {
// not the first parameter section; we must have a type
P.Error(P.pos, "type expected");
typ = AST.BadType;
@@ -365,18 +365,10 @@ func (P *Parser) ParseVarDeclList(list *AST.List, ellipsis_ok bool) {
} else {
// all list entries are types
// convert all type entries into type expressions
- if i0 > 0 {
- panic("internal parser error");
- }
-
for i, n := 0, list.len(); i < n; i++ {
t := list.at(i).(*AST.Type);
list.set(i, AST.NewTypeExpr(t));
}
-
- if P.tok == Scanner.COMMA {
- panic("internal parser error");
- }
}
P.Ecart();
@@ -514,6 +506,8 @@ func (P *Parser) ParseMapType() *AST.Type {\n }\n \n \n+func (P *Parser) ParseOperand() *AST.Expr\n+\n func (P *Parser) ParseStructType() *AST.Type {\n P.Trace("StructType");
\n @@ -522,10 +516,16 @@ func (P *Parser) ParseStructType() *AST.Type {\n if P.tok == Scanner.LBRACE {\n P.Next();\n t.list = AST.NewList();\n- for P.tok == Scanner.IDENT {\n+ for P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {\n P.ParseVarDeclList(t.list, false);\n- if P.tok != Scanner.RBRACE {\n- P.Expect(Scanner.SEMICOLON);\n+ if P.tok == Scanner.STRING {\n+ // ParseOperand takes care of string concatenation
+ t.list.Add(P.ParseOperand());\n+ }\n+ if P.tok == Scanner.SEMICOLON {\n+ P.Next();\n+ } else {\n+ break;\n }\n }\n \tP.OptSemicolon();
usr/gri/pretty/printer.go
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -139,7 +139,7 @@ func (P *Printer) Fields(list *AST.List) {\n for i, n := 0, list.len(); i < n; i++ {\n x := list.at(i).(*AST.Expr);\n if i > 0 {\n-\t\t\tif prev == Scanner.TYPE {\n+\t\t\tif prev == Scanner.TYPE && x.tok != Scanner.STRING || prev == Scanner.STRING {\n \t\t\t\tP.semi, P.newl = true, 1;\n \t\t\t} else if prev == x.tok {\n \t\t\t\tP.String(0, ", ");
usr/gri/pretty/selftest0.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 main
type Proto struct {
a int "a tag";
b, c, d *Proto "bcd" "tag";
*Proto "proto tag"
}
コアとなるコードの解説
parser.go
の変更解説
-
ParseVarDeclList
のellipsis_ok
の修正:- 変更前:
list.Add(P.ParseVarDecl(i0 > 0));
- 変更後:
list.Add(P.ParseVarDecl(ellipsis_ok /* param list */ && i0 > 0));
- この変更は、
ParseVarDecl
に渡される引数が、関数パラメータリストのコンテキストでのみellipsis_ok
(可変長引数を示す...
の許可)を考慮するように修正しています。これにより、構造体フィールド宣言の解析時に不必要なロジックが適用されるのを防ぎ、パーサーの正確性を向上させています。 - 同様に、
if i0 > 0 && typ == nil {
の条件もif ellipsis_ok /* param list */ && i0 > 0 && typ == nil {
に変更され、型が必須であるというチェックがパラメータリストのコンテキストに限定されました。 - また、
if i0 > 0 { panic("internal parser error"); }
やif P.tok == Scanner.COMMA { panic("internal parser error"); }
といった、型リストの処理における不要なpanic
が削除されました。これは、フィールドタグの導入により、型リストの解析ロジックがより柔軟になったため、これらの厳しすぎるチェックが不要になったことを示唆しています。
- 変更前:
-
ParseStructType
におけるフィールドタグの解析:- 変更前は、
for P.tok == Scanner.IDENT { ... }
のループで識別子(フィールド名)のみを期待していました。 - 変更後:
for P.tok != Scanner.RBRACE && P.tok != Scanner.EOF { ... }
のループに変更され、構造体の閉じブレース}
またはファイルの終端EOF
まで解析を続けるようになりました。 - 最も重要な変更は、このループ内に
if P.tok == Scanner.STRING { ... }
というブロックが追加されたことです。t.list.Add(P.ParseOperand());
が追加され、現在のトークンが文字列リテラル(Scanner.STRING
)である場合、それをParseOperand()
で解析し、構造体フィールドのリスト(t.list
)に追加するようにしました。ParseOperand()
は文字列リテラルの連結なども処理できるため、複数の文字列リテラルがタグとして連続して記述された場合にも対応できます。- これにより、パーサーは構造体フィールド宣言の後に続く文字列リテラルをフィールドタグとして正しく認識し、ASTに組み込むことができるようになりました。
- セミコロンの処理も調整され、フィールドタグの後にセミコロンが続く場合も正しく処理されるようになりました。
- 変更前は、
printer.go
の変更解説
Fields
関数における整形ロジックの修正:- 変更前:
if prev == Scanner.TYPE { P.semi, P.newl = true, 1; }
- 変更後:
if prev == Scanner.TYPE && x.tok != Scanner.STRING || prev == Scanner.STRING { P.semi, P.newl = true, 1; }
- この変更は、構造体フィールドの整形ルールを更新しています。
prev == Scanner.TYPE && x.tok != Scanner.STRING
: 前の要素が型であり、かつ現在の要素が文字列(フィールドタグ)ではない場合、改行とセミコロンを挿入します。これは通常のフィールド宣言(Name string
)の後に適用されます。prev == Scanner.STRING
: 前の要素が文字列(フィールドタグ)である場合、改行とセミコロンを挿入します。これは、フィールドタグの後に次のフィールド宣言が続く場合に適用され、タグが独立した要素として扱われることを保証します。
- このロジックにより、フィールドタグが構造体フィールド宣言の一部として正しく整形され、可読性の高い出力が生成されるようになります。
- 変更前:
selftest0.go
の追加解説
この新しいテストファイルは、以下のような構造体定義を含んでいます。
type Proto struct {
a int "a tag";
b, c, d *Proto "bcd" "tag";
*Proto "proto tag"
}
この定義は、Go言語のフィールドタグの様々な使用例を網羅しています。
a int "a tag";
: 単一のフィールドと単一のタグ。b, c, d *Proto "bcd" "tag";
: 複数のフィールドと複数のタグ。Goでは、複数の文字列リテラルを連続して記述することで、それらが連結されて一つのタグ文字列となるため、これは"bcdtag"
というタグになります。*Proto "proto tag"
: 埋め込みフィールド(匿名フィールド)とタグ。
このテストケースの追加により、パーサーとプリティプリンターがこれらの複雑なフィールドタグのシナリオを正しく処理できることが検証されます。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の仕様(Struct typesのセクション): https://go.dev/ref/spec#Struct_types
- Go言語のリフレクション(
reflect
パッケージ): https://pkg.go.dev/reflect
参考にした情報源リンク
- Go言語の公式ドキュメントおよび仕様書
- Go言語のソースコード(特に
go/parser
およびgo/printer
パッケージの歴史的なコミット) - Go言語の構造体タグに関する一般的な解説記事(Web検索を通じて得られた情報)
注記: このコミットはGo言語の非常に初期の段階のものであり、当時のusr/gri/pretty
パッケージは現在のgo/parser
やgo/printer
パッケージの直接の前身にあたるものと推測されます。現在のGo言語のパーサーやプリンターは、より洗練された実装になっていますが、このコミットはフィールドタグのサポートがどのように導入されたかを示す貴重な歴史的記録です。