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

[インデックス 1668] ファイルの概要

このコミットは、Go言語の初期のコンパイラツールチェーンの一部であるprettyパッケージ内のファイル群に対する変更を含んでいます。prettyパッケージは、Go言語のソースコードを解析し、抽象構文木(AST)を構築し、それを整形して出力する役割を担っていたと考えられます。

具体的に変更されたファイルは以下の通りです。

  • usr/gri/pretty/ast.go: 抽象構文木(AST)のノード定義が含まれています。Goプログラムの構造を表現するためのデータ構造が定義されています。
  • usr/gri/pretty/gds.go: Go Development Server (GDS) の一部である可能性があり、HTTPサーバーのロギングに関する修正が含まれています。
  • usr/gri/pretty/parser.go: Goソースコードを解析し、ASTを構築するパーサーのロジックが含まれています。このコミットの主要な変更点が含まれています。
  • usr/gri/pretty/printer.go: ASTを整形して出力するプリンターのロジックが含まれています。

コミット

- fixed bugs related to the empty statement
  (now in sync with the spec and with 6g)
- fixed incorrect logging statement in gds

R=r
OCL=24970
CL=24970

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/8e7873672e7d5b1f727fbc5d56ba4dea322d54f7

元コミット内容

commit 8e7873672e7d5b1f727fbc5d56ba4dea322d54f7
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Feb 12 16:06:21 2009 -0800

    - fixed bugs related to the empty statement
      (now in sync with the spec and with 6g)
    - fixed incorrect logging statement in gds
    
    R=r
    OCL=24970
    CL=24970
---
 usr/gri/pretty/ast.go     |  6 ++++++
 usr/gri/pretty/gds.go     |  2 +-\
 usr/gri/pretty/parser.go  | 33 +++++++++++++++------------------
 usr/gri/pretty/printer.go |  6 ++++++\
 4 files changed, 28 insertions(+), 19 deletions(-)

diff --git a/usr/gri/pretty/ast.go b/usr/gri/pretty/ast.go
index b3260a1fa4..df8bfbf2cd 100644
--- a/usr/gri/pretty/ast.go
+++ b/usr/gri/pretty/ast.go
@@ -390,6 +390,10 @@ type (
 		Tok int;  // BREAK, CONTINUE, GOTO, FALLTHROUGH
 		Label *Ident;  // if any, or nil
 	};
+	
+	EmptyStat struct {
+		Pos int;  // position of ";"
+	};
 )
 
 
@@ -405,6 +409,7 @@ type StatVisitor interface {
 	DoSwitchStat(s *SwitchStat);
 	DoSelectStat(s *SelectStat);
 	DoControlFlowStat(s *ControlFlowStat);
+	DoEmptyStat(s *EmptyStat);
 }
 
 
@@ -419,6 +424,7 @@ func (s *CaseClause) Visit(v StatVisitor) { v.DoCaseClause(s); }\
 func (s *SwitchStat) Visit(v StatVisitor) { v.DoSwitchStat(s); }\
 func (s *SelectStat) Visit(v StatVisitor) { v.DoSelectStat(s); }\
 func (s *ControlFlowStat) Visit(v StatVisitor) { v.DoControlFlowStat(s); }\
+func (s *EmptyStat) Visit(v StatVisitor) { v.DoEmptyStat(s); }\
 
 
 // ----------------------------------------------------------------------------
diff --git a/usr/gri/pretty/gds.go b/usr/gri/pretty/gds.go
index d5637cc9df..284a9d5073 100644
--- a/usr/gri/pretty/gds.go
+++ b/usr/gri/pretty/gds.go
@@ -155,7 +155,7 @@ func main() {\
 	http.Handle("/", http.HandlerFunc(serve));
 	err2 := http.ListenAndServe(":" + *port, nil);
 	if err2 != nil {\
-\t\tlog.Exitf("ListenAndServe: ", err2.String())\
+\t\tlog.Exitf("ListenAndServe: %s", err2.String())\
 	}\
 }\
 
diff --git a/usr/gri/pretty/parser.go b/usr/gri/pretty/parser.go
index b5cbcd72bf..5543d9eeb3 100644
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -733,25 +733,21 @@ func (P *Parser) parseStatementList(list *array.Array) {\
 		defer un(trace(P, "StatementList"));\
 	}\
 \n+\texpect_semi := false;\
 	for P.tok != Scanner.CASE && P.tok != Scanner.DEFAULT && P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {\
-\t\ts := P.parseStatement();\
-\t\tif s != nil {\
-\t\t\t// not the empty statement\
-\t\t\tlist.Push(s);\
+\t\tif expect_semi {\
+\t\t\tP.expect(Scanner.SEMICOLON);\
+\t\t\texpect_semi = false;\
 \t\t}\
+\t\tlist.Push(P.parseStatement());\
 \t\tif P.tok == Scanner.SEMICOLON {\
 \t\t\tP.next();\
 \t\t} else if P.opt_semi {\
 \t\t\tP.opt_semi = false;  // "consume" optional semicolon\
 \t\t} else {\
-\t\t\tbreak;\
+\t\t\texpect_semi = true;\
 \t\t}\
 \t}\
-\n-\t// Try to provide a good error message\n-\tif P.tok != Scanner.CASE && P.tok != Scanner.DEFAULT && P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {\
-\t\tP.error(P.pos, "expected end of statement list (semicolon missing?)");\
-\t}\
 }\
 \n \n@@ -1273,12 +1269,9 @@ func (P *Parser) parseIfStat() *AST.IfStat {\
 	var else_ AST.Stat;\
 	if P.tok == Scanner.ELSE {\
 		P.next();\
-\t\tif P.tok == Scanner.IF || P.tok == Scanner.LBRACE {\
+\t\tif ok := P.tok == Scanner.IF || P.tok == Scanner.LBRACE; ok || P.sixg {\
 \t\t\telse_ = P.parseStatement();\
-\t\t} else if P.sixg {\
-\t\t\telse_ = P.parseStatement();\
-\t\t\tif else_ != nil {\
-\t\t\t\t// not the empty statement\
+\t\t\tif !ok {\
 \t\t\t\t// wrap in a block since we don\'t have one\
 \t\t\t\tbody := AST.NewBlock(0, Scanner.LBRACE);\
 \t\t\t\tbody.List.Push(else_);\
@@ -1290,7 +1283,7 @@ func (P *Parser) parseIfStat() *AST.IfStat {\
 	}\
 	P.closeScope();\
 \n-\treturn &AST.IfStat{pos, init, cond, body, else_ };\
+\treturn &AST.IfStat{pos, init, cond, body, else_};\
 }\
 \n \n@@ -1438,10 +1431,14 @@ func (P *Parser) parseStatement() AST.Stat {\
 	\treturn P.parseSwitchStat();\
 	case Scanner.SELECT:\
 	\treturn P.parseSelectStat();\
+\tcase Scanner.SEMICOLON:\
+\t\t// don\'t consume the \";\", it is the separator following the empty statement\
+\t\treturn &AST.EmptyStat{P.pos};\
 \t}\
 \n-\t// empty statement\
-\treturn nil;\
+\t// no statement found\
+\tP.error(P.pos, "statement expected");\
+\treturn &AST.BadStat{P.pos};\
 }\
 \n \ndiff --git a/usr/gri/pretty/printer.go b/usr/gri/pretty/printer.go
index d29bfd1ee6..a0de7d06a6 100644
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -919,6 +919,12 @@ func (P *Printer) DoControlFlowStat(s *AST.ControlFlowStat) {\
 }\
 \n \n+func (P *Printer) DoEmptyStat(s *AST.EmptyStat) {\
+\tP.String(s.Pos, "");\
+\tP.separator = semicolon;\
+}\
+\n+\n // ----------------------------------------------------------------------------\
 // Declarations
 \n

変更の背景

このコミットの主な目的は、Go言語のパーサーとプリンターにおける「空ステートメント(empty statement)」の扱いに関するバグを修正し、Go言語の仕様(spec)および当時の主要なGoコンパイラである6gとの同期を図ることです。

Go言語では、セミコロン(;)のみで構成されるステートメントは「空ステートメント」として扱われます。これは、例えばループの本体が空である場合や、複数のステートメントを区切るために意図的に使用されることがあります。初期のGoコンパイラやツールでは、この空ステートメントの解釈や処理に不整合があったと考えられます。

具体的には、以下の問題が背景にあったと推測されます。

  1. パーサーの不正確な挙動: parser.goの変更から、パーサーが空ステートメントを正しく認識せず、ステートメントリストの終端を誤って判断したり、不要なエラーを報告したりするバグがあった可能性があります。特に、セミコロンの自動挿入(automatic semicolon insertion, ASI)のルールと空ステートメントの組み合わせで問題が生じていたかもしれません。
  2. AST表現の欠如: 空ステートメントをAST上で明示的に表現する型が存在しなかったため、パーサーが空ステートメントをスキップしたり、nilとして扱ったりしていた可能性があります。これにより、後続のツール(例えばプリンターやセマンティックアナライザー)が空ステートメントの存在を認識できず、正しく処理できない問題が発生していました。
  3. プリンターの不整合: ASTに空ステートメントが正しく表現されていないため、プリンターが空ステートメントを正しく出力できなかったり、フォーマットが崩れたりする問題があったと考えられます。
  4. 6gコンパイラとの互換性: 当時、Go言語の公式コンパイラとして6gが存在しており、prettyパッケージのようなツールは6gと同じ挙動を示す必要がありました。空ステートメントの扱いの不一致は、互換性の問題を引き起こす可能性がありました。
  5. Go言語仕様との同期: Go言語は初期段階から厳密な仕様策定が進められており、ツールがその仕様に準拠することは非常に重要でした。空ステートメントの扱いは、言語仕様で明確に定義されるべきものであり、このコミットはその定義に沿うようにツールを修正するものでした。

また、副次的な変更として、gds.goにおけるロギングステートメントの書式文字列のバグも修正されています。これは、ログ出力が意図した形式になっていなかったという軽微な問題です。

前提知識の解説

このコミットを理解するためには、以下の概念について知っておく必要があります。

1. 抽象構文木 (Abstract Syntax Tree, AST)

ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタープリタがソースコードを解析する際に、字句解析(トークン化)と構文解析(パース)を経て生成されます。ASTは、コードの意味を理解し、最適化やコード生成を行うための重要な中間表現となります。

  • ノード: ASTの各要素はノードと呼ばれ、変数宣言、関数呼び出し、制御構造(if文、for文など)、式、ステートメントなど、コードの様々な構成要素に対応します。
  • 階層構造: ノードは親子関係を持ち、コードの構造的なネストを表現します。例えば、if文のノードは、条件式、thenブロック、elseブロックのノードを子として持ちます。
  • ast.goの役割: このファイルは、Go言語のASTにおける様々なノードの型定義を含んでいます。例えば、Expr(式)、Stat(ステートメント)、Decl(宣言)などのインターフェースや、それらを実装する具体的な構造体(IfStat, ForStat, CallExprなど)が定義されています。

2. パーサー (Parser)

パーサーは、字句解析器(lexer/scanner)から受け取ったトークンのストリームを基に、言語の文法規則に従ってASTを構築する役割を担います。

  • 構文解析: トークン列が言語の文法に合致するかどうかを検証し、合致すればASTを生成します。
  • エラーハンドリング: 文法エラーを検出した場合、適切なエラーメッセージを生成し、可能であればエラーから回復して解析を続行しようとします。
  • parser.goの役割: このファイルは、Go言語のソースコードを読み込み、トークン化し、ASTを構築するロジックを含んでいます。parseStatementparseStatementListのような関数は、Goの文法規則に従ってステートメントを解析し、対応するASTノードを生成します。

3. プリンター (Printer)

プリンターは、ASTを受け取り、それを人間が読める形式のソースコードに変換して出力する役割を担います。これは、コードの整形(フォーマット)や、ASTをデバッグ目的で表示する際などに使用されます。

  • ASTの走査: ASTを深さ優先探索などで走査し、各ノードに対応するコード片を生成します。
  • フォーマット: インデント、改行、スペースなどを適切に挿入し、読みやすいコードを生成します。
  • printer.goの役割: このファイルは、ASTノードを走査し、Go言語のソースコードとして出力するロジックを含んでいます。DoEmptyStatのようなメソッドは、特定のASTノードタイプがどのように出力されるべきかを定義します。

4. 空ステートメント (Empty Statement)

多くのプログラミング言語において、空ステートメントは何も操作を行わないステートメントです。Go言語では、単一のセミコロン(;)が空ステートメントとして扱われます。

  • 用途:
    • ループの本体が空の場合: for {} のように、無限ループで何も処理しない場合など。
    • 意図的な区切り: コードの可読性を高めるため、あるいは特定の文法規則を満たすために使用されることがあります。
    • セミコロンの自動挿入 (ASI) との関係: Go言語にはASIのルールがあり、特定の状況で改行の後に自動的にセミコロンが挿入されます。これにより、開発者が明示的にセミコロンを書かなくても、コンパイラがステートメントの区切りを認識します。しかし、意図的に空ステートメントを記述する場合や、ASIのルールが適用されない状況では、明示的なセミコロンが必要になります。

5. 6gコンパイラ

6gは、Go言語の初期の公式コンパイラの一つで、Plan 9 Cコンパイラツールチェーンをベースにしていました。Go言語がオープンソース化された当初、6g(x86-64アーキテクチャ向け)、8g(x86-32アーキテクチャ向け)、5g(ARMアーキテクチャ向け)といったコンパイラが存在しました。これらのコンパイラは、Go言語の進化とともに、現在のgo tool compileに統合されていきました。このコミットの時点では、6gがGo言語の挙動の「基準」の一つであったため、prettyパッケージが6gと同期することは重要でした。

技術的詳細

このコミットは、主にGo言語のパーサーとASTの設計における空ステートメントの扱いを改善しています。

1. ast.goの変更

  • EmptyStat構造体の追加:
    type (
        // ...
        EmptyStat struct {
            Pos int;  // position of ";"
        };
    )
    
    これにより、空ステートメントがAST上で明示的なノードとして表現できるようになりました。Posフィールドは、ソースコード内でのセミコロンの位置を示します。
  • StatVisitorインターフェースへのDoEmptyStatメソッドの追加:
    type StatVisitor interface {
        // ...
        DoEmptyStat(s *EmptyStat);
    }
    
    ASTを走査するVisitorパターンにおいて、EmptyStatノードを処理するためのメソッドが追加されました。これにより、ASTを走査する他のツール(例えば、コード分析ツールやプリンター)が空ステートメントを認識し、適切に処理できるようになります。
  • EmptyStatVisitメソッドの実装:
    func (s *EmptyStat) Visit(v StatVisitor) { v.DoEmptyStat(s); }
    
    EmptyStat構造体がStatインターフェース(またはそれに準ずるもの)を実装し、Visitorを受け入れるためのVisitメソッドが追加されました。

これらの変更により、空ステートメントはASTの正式な一部となり、その存在がコンパイラツールチェーン全体で認識されるようになりました。

2. parser.goの変更

parser.goの変更は、空ステートメントの解析ロジックを根本的に修正しています。

  • parseStatementList関数の変更:
    • 変更前は、P.parseStatement()nilを返した場合(空ステートメントと解釈されていた場合)、そのステートメントはリストに追加されず、ループがbreakしていました。これは、空ステートメントがASTに表現されないため、パーサーがそれを「無視」していたことを示唆しています。
    • 変更後は、expect_semiというフラグが導入され、ステートメントの後にセミコロンが期待されるかどうかを管理します。
    • list.Push(P.parseStatement())という行により、parseStatementが返すASTノード(EmptyStatを含む)が常にステートメントリストに追加されるようになりました。
    • セミコロンの処理ロジックが変更され、P.tok == Scanner.SEMICOLONの場合にP.next()でセミコロンを消費し、P.opt_semi(オプションのセミコロン)の場合も同様に処理します。
    • 重要なのは、elseブロックでexpect_semi = trueが設定されるようになった点です。これは、ステートメントの後にセミコロンが明示的に存在しない場合、次のステートメントの前にセミコロンが期待されることを示唆しています。
    • 以前存在した「セミコロンが欠落している」というエラーメッセージを生成するロジックが削除されました。これは、空ステートメントの正しい解釈により、以前はエラーと見なされていた状況が正当な構文として扱われるようになったためと考えられます。
  • parseIfStat関数の変更:
    • if文のelseブロックの解析ロジックが簡素化されました。
    • 以前はP.sixg6gコンパイラモード)の場合に空ステートメントを特別扱いするようなロジックがありましたが、これが!okifまたは{でない場合)という条件に統合され、より一般的な方法でステートメントをブロックにラップするようになりました。これは、空ステートメントがASTノードとして表現されるようになったことで、特別なnilチェックが不要になったためと考えられます。
  • parseStatement関数の変更:
    • Scanner.SEMICOLON(セミコロン)が検出された場合、新しいAST.EmptyStatノードを返すようになりました。この際、セミコロン自体は消費されません。これは、セミコロンが空ステートメントの「区切り」であると同時に、それ自体が空ステートメントを構成する要素であるというGo言語の文法を反映しています。
    • 以前は、どのステートメントにもマッチしなかった場合にnilを返していましたが、変更後はP.errorを呼び出して「statement expected」(ステートメントが期待される)というエラーを報告し、AST.BadStatノードを返すようになりました。これは、パーサーがより厳密になり、予期しないトークンを空ステートメントとして黙って無視するのではなく、明示的なエラーとして扱うようになったことを示しています。

3. printer.goの変更

  • DoEmptyStatメソッドの追加:
    func (P *Printer) DoEmptyStat(s *AST.EmptyStat) {
        P.String(s.Pos, "");
        P.separator = semicolon;
    }
    
    EmptyStatノードがASTに導入されたことに伴い、プリンターがこのノードをどのように出力するかを定義するメソッドが追加されました。
    • P.String(s.Pos, ""): これは、空ステートメント自体は何も文字列を出力しないことを意味します。空ステートメントは単にセミコロンで表現されるため、プリンターはセミコロンを自動的に挿入するロジックを持っているか、またはセミコロンがステートメントの区切りとして別途処理されることを示唆しています。
    • P.separator = semicolon;: これは、空ステートメントの後にセミコロンが区切りとして続くことをプリンターに指示しています。

4. gds.goの変更

  • ロギング書式文字列の修正:
    -		log.Exitf("ListenAndServe: ", err2.String())
    +		log.Exitf("ListenAndServe: %s", err2.String())
    
    log.Exitf関数はfmt.Printfのような書式文字列を期待しますが、変更前は第2引数に書式指定子(%s)がありませんでした。これにより、err2.String()の内容が正しくログに出力されなかった可能性があります。変更後は%sが追加され、エラー文字列が正しく埋め込まれるようになりました。これは、Go言語の初期のロギングAPIの利用方法に関する軽微なバグ修正です。

コアとなるコードの変更箇所

このコミットのコアとなる変更は、usr/gri/pretty/ast.gousr/gri/pretty/parser.gousr/gri/pretty/printer.goにおける空ステートメントの扱いに関するものです。

usr/gri/pretty/ast.go

@@ -390,6 +390,10 @@ type (
 		Tok int;  // BREAK, CONTINUE, GOTO, FALLTHROUGH
 		Label *Ident;  // if any, or nil
 	};
+	
+	EmptyStat struct {
+		Pos int;  // position of ";"
+	};
 )
 
 
@@ -405,6 +409,7 @@ type StatVisitor interface {
 	DoSwitchStat(s *SwitchStat);
 	DoSelectStat(s *SelectStat);
 	DoControlFlowStat(s *ControlFlowStat);
+	DoEmptyStat(s *EmptyStat);
 }
 
 
@@ -419,6 +424,7 @@ func (s *CaseClause) Visit(v StatVisitor) { v.DoCaseClause(s); }\
 func (s *SwitchStat) Visit(v StatVisitor) { v.DoSwitchStat(s); }\
 func (s *SelectStat) Visit(v StatVisitor) { v.DoSelectStat(s); }\
 func (s *ControlFlowStat) Visit(v StatVisitor) { v.DoControlFlowStat(s); }\
+func (s *EmptyStat) Visit(v StatVisitor) { v.DoEmptyStat(s); }\

usr/gri/pretty/parser.go

@@ -733,25 +733,21 @@ func (P *Parser) parseStatementList(list *array.Array) {\
 		defer un(trace(P, "StatementList"));\
 	}\
 \n+\texpect_semi := false;\
 	for P.tok != Scanner.CASE && P.tok != Scanner.DEFAULT && P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {\
-\t\ts := P.parseStatement();\
-\t\tif s != nil {\
-\t\t\t// not the empty statement\
-\t\t\tlist.Push(s);\
+\t\tif expect_semi {\
+\t\t\tP.expect(Scanner.SEMICOLON);\
+\t\t\texpect_semi = false;\
 \t\t}\
+\t\tlist.Push(P.parseStatement());\
 \t\tif P.tok == Scanner.SEMICOLON {\
 \t\t\tP.next();\
 \t\t} else if P.opt_semi {\
 \t\t\tP.opt_semi = false;  // "consume" optional semicolon\
 \t\t} else {\
-\t\t\tbreak;\
+\t\t\texpect_semi = true;\
 \t\t}\
 \t}\
-\n-\t// Try to provide a good error message\n-\tif P.tok != Scanner.CASE && P.tok != Scanner.DEFAULT && P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {\
-\t\tP.error(P.pos, "expected end of statement list (semicolon missing?)");\
-\t}\
 }\
 \n \n@@ -1438,10 +1431,14 @@ func (P *Parser) parseStatement() AST.Stat {\
 	\treturn P.parseSwitchStat();\
 	case Scanner.SELECT:\
 	\treturn P.parseSelectStat();\
+\tcase Scanner.SEMICOLON:\
+\t\t// don\'t consume the \";\", it is the separator following the empty statement\
+\t\treturn &AST.EmptyStat{P.pos};\
 \t}\
 \n-\t// empty statement\
-\treturn nil;\
+\t// no statement found\
+\tP.error(P.pos, "statement expected");\
+\treturn &AST.BadStat{P.pos};\
 }\

usr/gri/pretty/printer.go

@@ -919,6 +919,12 @@ func (P *Printer) DoControlFlowStat(s *AST.ControlFlowStat) {\
 }\
 \n \n+func (P *Printer) DoEmptyStat(s *AST.EmptyStat) {\
+\tP.String(s.Pos, "");\
+\tP.separator = semicolon;\
+}\
+\n+\n // ----------------------------------------------------------------------------\
 // Declarations
 \n

コアとなるコードの解説

ast.goの変更

EmptyStat構造体の導入は、Go言語のASTが空ステートメントを明示的に表現するようになったことを意味します。これにより、パーサーは空ステートメントをnilとして扱うのではなく、具体的なASTノードとして生成できるようになります。StatVisitorインターフェースへのDoEmptyStatの追加は、ASTを走査する際に空ステートメントを特別に処理する必要があることを示しており、プリンターや他の分析ツールがこの新しいノードタイプに対応できるようになります。

parser.goの変更

parseStatementList関数における変更は、パーサーがステートメントリストを解析する際のロジックを大幅に改善しています。

  • 以前は、parseStatement()nilを返した場合(空ステートメントと見なされていた場合)、そのステートメントはリストに追加されず、ループが中断されていました。これは、空ステートメントがASTに表現されないため、パーサーがそれを「無視」していたことを示唆しています。
  • 新しいロジックでは、expect_semiフラグとlist.Push(P.parseStatement())の組み合わせにより、空ステートメントを含むすべてのステートメントがASTリストに正しく追加されるようになりました。これにより、ASTはソースコードの構造をより忠実に反映するようになります。
  • parseStatement関数におけるcase Scanner.SEMICOLON:の追加は、セミコロンが単独で出現した場合に、それをEmptyStatノードとして明示的に解析するようになったことを示しています。これにより、パーサーは空ステートメントを正しく識別し、ASTに組み込むことができます。
  • 以前のreturn nil;(空ステートメントとして扱われていた)からP.error(...)AST.BadStatを返すように変更されたことは、パーサーがより厳密になり、予期しないトークンをエラーとして扱うようになったことを示唆しています。これは、Go言語の文法解析の堅牢性を高めるための重要な変更です。

printer.goの変更

DoEmptyStatメソッドの追加は、EmptyStatノードがASTに導入されたことに伴い、プリンターがこのノードをどのように出力するかを定義しています。P.String(s.Pos, "")は、空ステートメント自体は何も文字列を出力しないことを意味します。これは、Go言語のセミコロンの自動挿入(ASI)のルールや、セミコロンがステートメントの区切りとして別途処理されることを考慮しているためと考えられます。P.separator = semicolon;は、空ステートメントの後にセミコロンが区切りとして続くことをプリンターに指示しており、これにより整形されたコードがGo言語の慣習に沿うように出力されます。

これらの変更全体として、Go言語の初期のコンパイラツールチェーンが、空ステートメントという言語の基本的な要素を、仕様と既存のコンパイラ(6g)の挙動に合わせて、より正確かつ堅牢に処理できるように進化している様子が伺えます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントおよび仕様
  • Go言語のGitHubリポジトリのコミット履歴
  • Go言語の初期のコンパイラに関する議論やドキュメント(ウェブ検索を通じて得られた情報)
  • 抽象構文木 (AST) およびコンパイラの基本に関する一般的な知識
  • プログラミング言語における空ステートメントの概念に関する一般的な知識
  • Go言語のセミコロン自動挿入 (ASI) に関する情報
  • log.ExitfのようなGo言語の初期のロギングAPIに関する情報