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

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

このコミットは、Go言語のコンパイラの一部であるsrc/lib/go/parser.goファイルに対する変更です。parser.goは、Go言語のソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を構築する役割を担っています。具体的には、字句解析器(lexer)から受け取ったトークンストリームを文法規則に従って解釈し、プログラムの構造を表現するASTノードを生成します。このファイルは、Goコンパイラのフロントエンドにおける中核的なコンポーネントの一つであり、言語の構文規則がどのように解釈されるかを定義しています。

コミット

このコミットは、Go言語の関数リテラル(無名関数)の後にセミコロン(;)の区切り文字を必須とする変更を導入しています。これにより、関数リテラルが文の終わりを示す際に、明示的なセミコロンが必要となり、パーサーの挙動がより明確になります。

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

https://github.com/golang/go/commit/27d1159ab4ef75b73ad22c934de176337d0d852b

元コミット内容

commit 27d1159ab4ef75b73ad22c934de176337d0d852b
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Apr 2 22:59:57 2009 -0700

    require ";" separator after function literals
    
    R=rsc
    DELTA=1  (1 added, 0 deleted, 0 changed)
    OCL=27057
    CL=27059
---
 src/lib/go/parser.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/lib/go/parser.go b/src/lib/go/parser.go
index 038b27266b..6cabaa7fe7 100644
--- a/src/lib/go/parser.go
+++ b/src/lib/go/parser.go
@@ -824,6 +824,7 @@ func (p *parser) parseFuncLit() ast.Expr {
 	typ := p.parseFuncType();
 	p.expr_lev++;
 	body := p.parseBlockStmt();
+	p.opt_semi = false;  // function body requires separating ";"
 	p.expr_lev--;
 
 	return &ast.FuncLit{typ, body};

変更の背景

Go言語は、その設計初期段階から、コードの可読性と記述の簡潔さを重視してきました。その一環として、多くのC言語系の言語とは異なり、文の終わりにセミコロンを明示的に記述することを必須としない「自動セミコロン挿入(Automatic Semicolon Insertion: ASI)」というメカニズムを採用しています。しかし、ASIは常に完璧に機能するわけではなく、特定の構文構造、特に複雑な式やブロックの後に曖昧さを生じさせることがあります。

このコミットが行われた2009年4月は、Go言語がまだ活発に開発され、言語仕様が固まりつつあった時期です。関数リテラルは、Go言語の強力な機能の一つであり、クロージャや高階関数を記述する際に不可欠です。しかし、関数リテラルの本体(ブロックステートメント)の直後に続くコードが、パーサーにとって曖昧な解釈を招く可能性がありました。例えば、関数リテラルの直後に別の文が続く場合、パーサーは関数リテラルが文の終わりを示しているのか、それとも後続のコードと結合して一つの大きな式を形成しているのかを判断する必要がありました。

このような曖昧さを解消し、パーサーの堅牢性を高めるために、関数リテラルの本体の直後には明示的なセミコロンを要求するという決定がなされました。これにより、パーサーは関数リテラルが独立した文として扱われることを確実に認識できるようになり、予期せぬ構文エラーや解釈の誤りを防ぐことができます。

前提知識の解説

1. Go言語の構文解析 (Parsing)

Go言語のコンパイラは、ソースコードを機械が理解できる形式に変換する過程で、いくつかの段階を経ます。構文解析はその主要な段階の一つです。

  • 字句解析 (Lexical Analysis): ソースコードを最小単位の「トークン」(キーワード、識別子、演算子、リテラルなど)に分解します。
  • 構文解析 (Syntactic Analysis): トークンのストリームを文法規則に従って解析し、プログラムの構造を抽象構文木(AST)として表現します。ASTは、プログラムの階層的な構造を木構造で表したもので、コンパイラの後の段階(意味解析、コード生成など)で利用されます。

src/lib/go/parser.goは、この構文解析のロジックを実装しているファイルです。

2. 抽象構文木 (AST: Abstract Syntax Tree)

ASTは、ソースコードの抽象的な構文構造を表現する木構造のデータ構造です。各ノードはソースコード内の構文要素(式、文、宣言など)を表し、子ノードはそれらの要素の構成要素を表します。例えば、関数呼び出しはASTノードとして表現され、その子ノードには関数名を表す識別子と引数を表す式が含まれます。

3. 関数リテラル (Function Literals)

Go言語における関数リテラルは、名前を持たない関数(無名関数)をその場で定義し、式として使用できる構文です。これらはクロージャとして機能し、定義されたスコープの変数をキャプチャできます。

例:

func() {
    fmt.Println("Hello from a function literal!")
}() // ここで関数リテラルが呼び出される

4. 自動セミコロン挿入 (Automatic Semicolon Insertion - ASI)

Go言語の文法では、文の区切り文字としてセミコロンが使用されます。しかし、ほとんどの場合、プログラマが明示的にセミコロンを記述する必要はありません。これは、Goコンパイラが特定のルールに基づいて自動的にセミコロンを挿入するためです。

ASIの基本的なルールは以下の通りです。

  • 改行が、文を終了できるトークン(識別子、数値リテラル、文字列リテラル、break, continue, fallthrough, returnなどのキーワード、++, --, ), ], }などの演算子や句読点)の後に続く場合、その改行の前にセミコロンが自動的に挿入されます。
  • これにより、ほとんどのGoコードではセミコロンを省略できます。
  • ただし、if, for, funcなどの制御構造の後に続く{を新しい行に配置すると、{の前にセミコロンが挿入されてしまい、構文エラーとなる一般的な落とし穴があります。

このコミットは、ASIの一般的なルールとは異なり、関数リテラルの後にセミコロンを「必須」とすることで、特定の構文の曖昧さを解消しようとしています。

技術的詳細

src/lib/go/parser.go内のparseFuncLit()関数は、Go言語のパーサーが関数リテラルを解析する際に呼び出されるメソッドです。この関数は、関数リテラルの型(引数と戻り値のシグネチャ)と、その本体(関数が実行するコードブロック)を解析し、最終的にast.FuncLitというASTノードを構築して返します。

コミットの変更は、parseFuncLit()関数内で、関数リテラルの本体(body := p.parseBlockStmt();)が解析された直後に行われています。追加された行はp.opt_semi = false;です。

Go言語のパーサーは、parser構造体内にopt_semiのようなフラグを持っていることが一般的です。このフラグは、現在のパーサーの状態において、セミコロンが「オプション」であるか「必須」であるかを制御するために使用されます。

  • p.opt_semi = true; のような設定は、パーサーが現在の位置でセミコロンを期待するが、それが存在しなくてもエラーとしない(ASIによって挿入される可能性があるため)ことを意味する可能性があります。
  • p.opt_semi = false; のような設定は、パーサーが現在の位置でセミコロンを「必須」として期待し、それが存在しない場合は構文エラーとして扱うことを意味します。

このコミットでは、関数リテラルの本体が解析された直後にp.opt_semi = false;を設定しています。これは、関数リテラルの本体が終了した直後には、必ずセミコロン(明示的またはASIによるもの)が存在しなければならないというルールをパーサーに強制しています。コメント// function body requires separating ";"がこの意図を明確に示しています。

この変更の目的は、関数リテラルが文の終わりを示す境界を明確にすることです。例えば、以下のようなコードがあった場合を考えます(Go言語の初期の構文を仮定)。

func() { /* ... */ } // 関数リテラル
x := 1              // 別の文

もし関数リテラルの後にセミコロンが必須でなかった場合、パーサーは}の直後に続くxを、関数リテラルの一部として解釈しようとするか、あるいは別の文の開始として解釈するかで曖昧さが生じる可能性がありました。p.opt_semi = false;とすることで、パーサーは}の直後に必ず文の区切り(セミコロン)が来ると期待し、その後のx := 1を新しい文として正しく認識できるようになります。これにより、パーサーのロジックが簡素化され、構文解析の堅牢性が向上します。

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

--- a/src/lib/go/parser.go
+++ b/src/lib/go/parser.go
@@ -824,6 +824,7 @@ func (p *parser) parseFuncLit() ast.Expr {
 	typ := p.parseFuncType();
 	p.expr_lev++;
 	body := p.parseBlockStmt();
+	p.opt_semi = false;  // function body requires separating ";"
 	p.expr_lev--;
 
 	return &ast.FuncLit{typ, body};

コアとなるコードの解説

変更はsrc/lib/go/parser.goファイルのparseFuncLit()関数内、824行目付近にあります。

  • typ := p.parseFuncType();:関数リテラルの型(引数と戻り値のシグネチャ)を解析します。
  • p.expr_lev++;:式の解析レベルをインクリメントします。これはパーサーが現在どの程度の深さで式を解析しているかを追跡するための内部的なカウンタである可能性があります。
  • body := p.parseBlockStmt();:関数リテラルの本体であるブロックステートメント({ ... }で囲まれたコード)を解析します。この行が実行された時点で、関数リテラルの内部のコードは全て解析済みです。
  • p.opt_semi = false; // function body requires separating ";"この行が今回のコミットによる追加です。 p.opt_semiというパーサーの内部フラグをfalseに設定しています。このフラグは、パーサーが現在の位置でセミコロンをオプションとして扱うかどうかを制御していると考えられます。falseに設定することで、関数リテラルの本体が終了した直後には、明示的または自動挿入されたセミコロンが必須であることをパーサーに強制します。これにより、関数リテラルが独立した文として扱われることが保証され、その後の構文解析の曖昧さが解消されます。
  • p.expr_lev--;:式の解析レベルをデクリメントします。
  • return &ast.FuncLit{typ, body};:解析された型と本体を持つast.FuncLitノードを返します。

この変更は、Go言語の構文解析器が関数リテラルをどのように扱うかという、言語の根幹に関わる部分に影響を与えています。特に、自動セミコロン挿入のルールが適用される文脈において、関数リテラルの後の区切りを明確にすることで、パーサーの挙動を予測可能にし、言語の堅牢性を高めることに貢献しています。

関連リンク

参考にした情報源リンク

  • Go言語の自動セミコロン挿入に関するWeb検索結果
  • Go言語の公式ドキュメント (Go Programming Language Specification, Go Blog)
  • Go言語のパーサーの一般的な動作に関する知識```markdown

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

このコミットは、Go言語のコンパイラの一部であるsrc/lib/go/parser.goファイルに対する変更です。parser.goは、Go言語のソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)を構築する役割を担っています。具体的には、字句解析器(lexer)から受け取ったトークンストリームを文法規則に従って解釈し、プログラムの構造を表現するASTノードを生成します。このファイルは、Goコンパイラのフロントエンドにおける中核的なコンポーネントの一つであり、言語の構文規則がどのように解釈されるかを定義しています。

コミット

このコミットは、Go言語の関数リテラル(無名関数)の後にセミコロン(;)の区切り文字を必須とする変更を導入しています。これにより、関数リテラルが文の終わりを示す際に、明示的なセミコロンが必要となり、パーサーの挙動がより明確になります。

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

https://github.com/golang/go/commit/27d1159ab4ef75b73ad22c934de176337d0d852b

元コミット内容

commit 27d1159ab4ef75b73ad22c934de176337d0d852b
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Apr 2 22:59:57 2009 -0700

    require ";" separator after function literals
    
    R=rsc
    DELTA=1  (1 added, 0 deleted, 0 changed)
    OCL=27057
    CL=27059
---
 src/lib/go/parser.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/lib/go/parser.go b/src/lib/go/parser.go
index 038b27266b..6cabaa7fe7 100644
--- a/src/lib/go/parser.go
+++ b/lib/go/parser.go
@@ -824,6 +824,7 @@ func (p *parser) parseFuncLit() ast.Expr {
 	typ := p.parseFuncType();
 	p.expr_lev++;
 	body := p.parseBlockStmt();
+	p.opt_semi = false;  // function body requires separating ";"
 	p.expr_lev--;
 
 	return &ast.FuncLit{typ, body};

変更の背景

Go言語は、その設計初期段階から、コードの可読性と記述の簡潔さを重視してきました。その一環として、多くのC言語系の言語とは異なり、文の終わりにセミコロンを明示的に記述することを必須としない「自動セミコロン挿入(Automatic Semicolon Insertion: ASI)」というメカニズムを採用しています。しかし、ASIは常に完璧に機能するわけではなく、特定の構文構造、特に複雑な式やブロックの後に曖昧さを生じさせることがあります。

このコミットが行われた2009年4月は、Go言語がまだ活発に開発され、言語仕様が固まりつつあった時期です。関数リテラルは、Go言語の強力な機能の一つであり、クロージャや高階関数を記述する際に不可欠です。しかし、関数リテラルの本体(ブロックステートメント)の直後に続くコードが、パーサーにとって曖昧な解釈を招く可能性がありました。例えば、関数リテラルの直後に別の文が続く場合、パーサーは関数リテラルが文の終わりを示しているのか、それとも後続のコードと結合して一つの大きな式を形成しているのかを判断する必要がありました。

このような曖昧さを解消し、パーサーの堅牢性を高めるために、関数リテラルの本体の直後には明示的なセミコロンを要求するという決定がなされました。これにより、パーサーは関数リテラルが独立した文として扱われることを確実に認識できるようになり、予期せぬ構文エラーや解釈の誤りを防ぐことができます。

前提知識の解説

1. Go言語の構文解析 (Parsing)

Go言語のコンパイラは、ソースコードを機械が理解できる形式に変換する過程で、いくつかの段階を経ます。構文解析はその主要な段階の一つです。

  • 字句解析 (Lexical Analysis): ソースコードを最小単位の「トークン」(キーワード、識別子、演算子、リテラルなど)に分解します。
  • 構文解析 (Syntactic Analysis): トークンのストリームを文法規則に従って解析し、プログラムの構造を抽象構文木(AST)として表現します。ASTは、プログラムの階層的な構造を木構造で表したもので、コンパイラの後の段階(意味解析、コード生成など)で利用されます。

src/lib/go/parser.goは、この構文解析のロジックを実装しているファイルです。

2. 抽象構文木 (AST: Abstract Syntax Tree)

ASTは、ソースコードの抽象的な構文構造を表現する木構造のデータ構造です。各ノードはソースコード内の構文要素(式、文、宣言など)を表し、子ノードはそれらの要素の構成要素を表します。例えば、関数呼び出しはASTノードとして表現され、その子ノードには関数名を表す識別子と引数を表す式が含まれます。

3. 関数リテラル (Function Literals)

Go言語における関数リテラルは、名前を持たない関数(無名関数)をその場で定義し、式として使用できる構文です。これらはクロージャとして機能し、定義されたスコープの変数をキャプチャできます。

例:

func() {
    fmt.Println("Hello from a function literal!")
}() // ここで関数リテラルが呼び出される

4. 自動セミコロン挿入 (Automatic Semicolon Insertion - ASI)

Go言語の文法では、文の区切り文字としてセミコロンが使用されます。しかし、ほとんどの場合、プログラマが明示的にセミコロンを記述する必要はありません。これは、Goコンパイラが特定のルールに基づいて自動的にセミコロンを挿入するためです。

ASIの基本的なルールは以下の通りです。

  • 改行が、文を終了できるトークン(識別子、数値リテラル、文字列リテラル、break, continue, fallthrough, returnなどのキーワード、++, --, ), ], }などの演算子や句読点)の後に続く場合、その改行の前にセミコロンが自動的に挿入されます。
  • これにより、ほとんどのGoコードではセミコロンを省略できます。
  • ただし、if, for, funcなどの制御構造の後に続く{を新しい行に配置すると、{の前にセミコロンが挿入されてしまい、構文エラーとなる一般的な落とし穴があります。

このコミットは、ASIの一般的なルールとは異なり、関数リテラルの後にセミコロンを「必須」とすることで、特定の構文の曖昧さを解消しようとしています。

技術的詳細

src/lib/go/parser.go内のparseFuncLit()関数は、Go言語のパーサーが関数リテラルを解析する際に呼び出されるメソッドです。この関数は、関数リテラルの型(引数と戻り値のシグネチャ)と、その本体(関数が実行するコードブロック)を解析し、最終的にast.FuncLitというASTノードを構築して返します。

コミットの変更は、parseFuncLit()関数内で、関数リテラルの本体(body := p.parseBlockStmt();)が解析された直後に行われています。追加された行はp.opt_semi = false;です。

Go言語のパーサーは、parser構造体内にopt_semiのようなフラグを持っていることが一般的です。このフラグは、現在のパーサーの状態において、セミコロンが「オプション」であるか「必須」であるかを制御するために使用されます。

  • p.opt_semi = true; のような設定は、パーサーが現在の位置でセミコロンを期待するが、それが存在しなくてもエラーとしない(ASIによって挿入される可能性があるため)ことを意味する可能性があります。
  • p.opt_semi = false; のような設定は、パーサーが現在の位置でセミコロンを「必須」として期待し、それが存在しない場合は構文エラーとして扱うことを意味します。

このコミットでは、関数リテラルの本体が解析された直後にp.opt_semi = false;を設定しています。これは、関数リテラルの本体が終了した直後には、必ずセミコロン(明示的またはASIによるもの)が存在しなければならないというルールをパーサーに強制しています。コメント// function body requires separating ";"がこの意図を明確に示しています。

この変更の目的は、関数リテラルが文の終わりを示す境界を明確にすることです。例えば、以下のようなコードがあった場合を考えます(Go言語の初期の構文を仮定)。

func() { /* ... */ } // 関数リテラル
x := 1              // 別の文

もし関数リテラルの後にセミコロンが必須でなかった場合、パーサーは}の直後に続くxを、関数リテラルの一部として解釈しようとするか、あるいは別の文の開始として解釈するかで曖昧さが生じる可能性がありました。p.opt_semi = false;とすることで、パーサーは}の直後に必ず文の区切り(セミコロン)が来ると期待し、その後のx := 1を新しい文として正しく認識できるようになります。これにより、パーサーのロジックが簡素化され、構文解析の堅牢性が向上します。

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

--- a/src/lib/go/parser.go
+++ b/lib/go/parser.go
@@ -824,6 +824,7 @@ func (p *parser) parseFuncLit() ast.Expr {
 	typ := p.parseFuncType();
 	p.expr_lev++;
 	body := p.parseBlockStmt();
+	p.opt_semi = false;  // function body requires separating ";"
 	p.expr_lev--;
 
 	return &ast.FuncLit{typ, body};

コアとなるコードの解説

変更はsrc/lib/go/parser.goファイルのparseFuncLit()関数内、824行目付近にあります。

  • typ := p.parseFuncType();:関数リテラルの型(引数と戻り値のシグネチャ)を解析します。
  • p.expr_lev++;:式の解析レベルをインクリメントします。これはパーサーが現在どの程度の深さで式を解析しているかを追跡するための内部的なカウンタである可能性があります。
  • body := p.parseBlockStmt();:関数リテラルの本体であるブロックステートメント({ ... }で囲まれたコード)を解析します。この行が実行された時点で、関数リテラルの内部のコードは全て解析済みです。
  • p.opt_semi = false; // function body requires separating ";"この行が今回のコミットによる追加です。 p.opt_semiというパーサーの内部フラグをfalseに設定しています。このフラグは、パーサーが現在の位置でセミコロンをオプションとして扱うかどうかを制御していると考えられます。falseに設定することで、関数リテラルの本体が終了した直後には、明示的または自動挿入されたセミコロンが必須であることをパーサーに強制します。これにより、関数リテラルが独立した文として扱われることが保証され、その後の構文解析の曖昧さが解消されます。
  • p.expr_lev--;:式の解析レベルをデクリメントします。
  • return &ast.FuncLit{typ, body};:解析された型と本体を持つast.FuncLitノードを返します。

この変更は、Go言語の構文解析器が関数リテラルをどのように扱うかという、言語の根幹に関わる部分に影響を与えています。特に、自動セミコロン挿入のルールが適用される文脈において、関数リテラルの後の区切りを明確にすることで、パーサーの挙動を予測可能にし、言語の堅牢性を高めることに貢献しています。

関連リンク

参考にした情報源リンク

  • Go言語の自動セミコロン挿入に関するWeb検索結果
  • Go言語の公式ドキュメント (Go Programming Language Specification, Go Blog)
  • Go言語のパーサーの一般的な動作に関する知識