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

[インデックス 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.gousr/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.goselftest1.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の変更解説

  1. ParseVarDeclListellipsis_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が削除されました。これは、フィールドタグの導入により、型リストの解析ロジックがより柔軟になったため、これらの厳しすぎるチェックが不要になったことを示唆しています。
  2. 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の変更解説

  1. 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言語の公式ドキュメントおよび仕様書
  • Go言語のソースコード(特にgo/parserおよびgo/printerパッケージの歴史的なコミット)
  • Go言語の構造体タグに関する一般的な解説記事(Web検索を通じて得られた情報)

注記: このコミットはGo言語の非常に初期の段階のものであり、当時のusr/gri/prettyパッケージは現在のgo/parsergo/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.gousr/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.goselftest1.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の変更解説

  1. ParseVarDeclListellipsis_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が削除されました。これは、フィールドタグの導入により、型リストの解析ロジックがより柔軟になったため、これらの厳しすぎるチェックが不要になったことを示唆しています。
  2. 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の変更解説

  1. 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言語の公式ドキュメントおよび仕様書
  • Go言語のソースコード(特にgo/parserおよびgo/printerパッケージの歴史的なコミット)
  • Go言語の構造体タグに関する一般的な解説記事(Web検索を通じて得られた情報)

注記: このコミットはGo言語の非常に初期の段階のものであり、当時のusr/gri/prettyパッケージは現在のgo/parsergo/printerパッケージの直接の前身にあたるものと推測されます。現在のGo言語のパーサーやプリンターは、より洗練された実装になっていますが、このコミットはフィールドタグのサポートがどのように導入されたかを示す貴重な歴史的記録です。