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

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

このコミットは、Go言語のコンパイラの一部である src/cmd/gc/go.y ファイルに対する変更です。go.y は、Go言語の構文解析器(パーサー)を生成するためのYacc(Yet Another Compiler-Compiler)またはBisonの文法定義ファイルです。Goコンパイラは、このファイルに記述された文法規則に基づいてGoのソースコードを解析し、抽象構文木(AST)を構築します。具体的には、for ループ、特に for...range 構文の解析に関連する文法規則が変更されています。

コミット

このコミットは、Go言語のコンパイラにおける range 構文の文法定義を整理し、クリーンアップすることを目的としています。具体的には、orange_stmt という非終端記号を range_stmt に改名し、for ループのヘッダー部分の文法規則を簡素化しています。これにより、コンパイラのコードベースの可読性と保守性が向上し、将来的な言語機能の拡張に備えるものと考えられます。

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

https://github.com/golang/go/commit/3ec4675220f18c3bc6680e71c6ce09a76b641c8c

元コミット内容

commit 3ec4675220f18c3bc6680e71c6ce09a76b641c8c
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jan 28 15:41:50 2009 -0800

    clean up range grammar
    
    R=ken
    OCL=23712
    CL=23714
---
 src/cmd/gc/go.y | 27 ++++++++++-----------------
 1 file changed, 10 insertions(+), 17 deletions(-)

diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y
index 4aafd0b0c1..d04991dc47 100644
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -52,7 +52,7 @@
 %type	<node>		Astmt Bstmt
 %type	<node>		for_stmt for_body for_header
 %type	<node>		if_stmt if_body if_header select_stmt
-%type	<node>		simple_stmt osimple_stmt orange_stmt semi_stmt
+%type	<node>		simple_stmt osimple_stmt range_stmt semi_stmt
 %type	<node>		expr uexpr pexpr expr_list oexpr oexpr_list expr_list_r
 %type	<node>		exprsym3_list_r exprsym3
 %type	<node>		name onew_name new_name new_name_list_r new_field
@@ -536,9 +536,8 @@ compound_stmt:
 		popdcl();
 	}
 
-orange_stmt:
-	osimple_stmt
-|	exprsym3_list_r '=' LRANGE expr
+range_stmt:
+	exprsym3_list_r '=' LRANGE expr
 	{
 		$$ = nod(ORANGE, $1, $4);
 		$$->etype = 0;	// := flag
@@ -550,14 +549,8 @@ orange_stmt:
 	}
 
 for_header:
-\tosimple_stmt ';' orange_stmt ';' osimple_stmt
+\tosimple_stmt ';' osimple_stmt ';' osimple_stmt
 	{
-\t\tif($3 != N && $3->op == ORANGE) {\n-\t\t\t$$ = dorange($3);\n-\t\t\t$$->ninit = list($$->ninit, $1);\n-\t\t\t$$->nincr = list($$->nincr, $5);\n-\t\t\tbreak;\n-\t\t}\n \t\t// init ; test ; incr
 \t\tif($5 != N && $5->colas != 0)
 \t\t\tyyerror("cannot declare in the for-increment");
 \t\t$$ = nod(OFOR, N, N);
@@ -566,19 +559,19 @@ for_header:
 \t\t$$->ntest = $3;
 \t\t$$->nincr = $5;
 	}
-|\torange_stmt
+|\tosimple_stmt
 	{
-\t\t// range
-\t\tif($1 != N && $1->op == ORANGE) {\n-\t\t\t$$ = dorange($1);\n-\t\t\tbreak;\n-\t\t}\n \t\t// normal test
 \t\t$$ = nod(OFOR, N, N);
 \t\t$$->ninit = N;
 \t\t$$->ntest = $1;
 \t\t$$->nincr = N;
 	}
+|\trange_stmt
+\t{\n+\t\t$$ = dorange($1);\n+\t\taddtotop($$);\n+\t}\
 
 for_body:
 	for_header compound_stmt

変更の背景

このコミットが行われた2009年1月は、Go言語がまだ一般に公開される前の非常に初期の開発段階でした。Go言語の設計と実装は活発に進められており、文法や内部構造は頻繁に変更されていました。

この「clean up range grammar」というコミットメッセージは、for...range ループの文法解析に関する既存の実装が、何らかの理由で最適ではない、あるいは将来の拡張性を考慮すると問題があると考えられたことを示唆しています。具体的な背景としては、以下のような点が考えられます。

  1. 文法の整理と簡素化: orange_stmt という名前が range 構文を直接的に示していないため、より分かりやすい range_stmt への改名が行われた可能性があります。また、for_header 内での orange_stmt の扱いが冗長であったり、他の for ループの形式と統合されていなかったりしたため、文法規則を整理し、より一貫性のある解析ロジックを目指したと考えられます。
  2. コードの可読性と保守性の向上: コンパイラのコードベースは複雑になりがちです。文法定義をクリーンアップすることで、将来の機能追加やバグ修正が容易になります。
  3. 解析効率の改善: 文法規則の整理は、パーサーの効率をわずかに向上させる可能性もあります。特に、曖昧な規則や重複する規則を排除することで、解析時のバックトラックを減らすことができます。
  4. 言語仕様の進化への対応: Go言語の for...range 構文は、スライス、配列、文字列、マップ、チャネルなど、様々なデータ構造をイテレートするために使用されます。初期の段階では、これらのイテレーションのセマンティクスがまだ完全に固まっていなかった可能性があり、文法を整理することで、将来の言語仕様の変更に柔軟に対応できるようにしたと考えられます。

要するに、この変更は、Go言語の for...range 構文の解析をより堅牢で、理解しやすく、保守しやすいものにするための、初期段階における重要なリファクタリングの一環であったと言えます。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

1. Yacc/Bisonと文法定義

go.y ファイルは、Yacc(Yet Another Compiler-Compiler)またはそのGNU版であるBisonの入力ファイルです。Yacc/Bisonは、BNF(Backus-Naur Form)に似た形式で記述された文法規則から、C言語(または他の言語)の構文解析器(パーサー)を自動生成するツールです。

  • 文法規則(Grammar Rules): プログラミング言語の構文を定義する一連の規則です。例えば、「式は項の後に演算子と項が続く」といった形です。
  • 非終端記号(Non-terminal Symbols): 文法規則の左辺に現れる記号で、さらに別の規則によって展開される記号です。例: expression, statement, for_header
  • 終端記号(Terminal Symbols): 文法規則の右辺に現れる記号で、これ以上展開されない記号です。通常、字句解析器(レキサー)によって識別されるトークンに対応します。例: ID (識別子), NUMBER (数値), '+', ';', LRANGE (Goの range キーワードに対応するトークン)。
  • アクション(Actions): 各文法規則の右辺に付随するC言語のコードブロックです。パーサーがその規則にマッチしたときに実行されます。通常、抽象構文木(AST)のノードを構築したり、セマンティックチェックを行ったりします。
    • $$: 現在の規則の左辺の非終端記号に対応する値(通常はASTノードへのポインタ)を表します。
    • $1, $2, ...: 現在の規則の右辺の記号に対応する値(ASTノードへのポインタなど)を表します。$1 は最初の記号、$2 は2番目の記号、といった具合です。
  • %type <node>: Yacc/Bisonのディレクティブで、特定の非終端記号がどのような型の値を保持するかを宣言します。ここでは、node 型(おそらくASTノードを表す構造体へのポインタ)を保持することを示しています。

2. Go言語の for ループと for...range

Go言語には、いくつかの for ループの形式があります。

  • 通常の for ループ: C言語やJavaに似た形式で、初期化ステートメント、条件式、後処理ステートメントを持ちます。
    for init; condition; post {
        // ...
    }
    
  • 条件式のみの for ループ:
    for condition {
        // ...
    }
    
  • 無限ループ:
    for {
        // ...
    }
    
  • for...range ループ: 配列、スライス、文字列、マップ、チャネルなどの要素をイテレートするために使用されます。
    for key, value := range collection {
        // ...
    }
    
    または
    for _, value := range collection {
        // ...
    }
    
    または
    for key := range collection {
        // ...
    }
    

このコミットは、特に for...range 構文の解析に関連しています。

3. Goコンパイラの内部構造(初期段階)

Goコンパイラは、ソースコードを機械語に変換する過程で、以下の主要なフェーズを経ます。

  1. 字句解析(Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。
  2. 構文解析(Syntax Analysis): トークンのストリームを文法規則に基づいて解析し、抽象構文木(AST)を構築します。go.y がこのフェーズで使用されます。
  3. 意味解析(Semantic Analysis): ASTに対して型チェック、名前解決、スコープチェックなどの意味的な検証を行います。
  4. 中間コード生成(Intermediate Code Generation): ASTをより低レベルの中間表現に変換します。
  5. 最適化(Optimization): 中間コードを最適化します。
  6. コード生成(Code Generation): 最終的な機械語コードを生成します。

このコミットは、構文解析フェーズにおける for...range 構文のAST構築ロジックに影響を与えます。

  • nod(OP, Left, Right): ASTノードを作成する関数。OP はノードの種類(例: ORANGEfor...range ループを表す内部オペレーションコード)、LeftRight はそのノードの子ノードです。
  • $$->ninit, $$->ntest, $$->nincr: for ループのASTノードのフィールドで、それぞれ初期化ステートメント、条件式、インクリメントステートメントに対応する子ノードを格納します。
  • dorange($1): range 構文のASTノードを処理し、適切な for ループのAST構造に変換する関数。
  • addtotop($$): 生成されたASTノードを、現在のスコープのトップレベルに追加する関数。
  • list(list_head, new_node): ASTノードのリストに新しいノードを追加する関数。
  • N: nil または null に相当する、ノードが存在しないことを示す定数。
  • yyerror("message"): 構文エラーを報告する関数。

技術的詳細

このコミットの主要な変更点は、go.y ファイルにおける range 構文の文法規則の再定義と、それに伴う for_header 規則の簡素化です。

1. orange_stmt から range_stmt への改名と定義の変更

変更前:

orange_stmt:
	osimple_stmt
|	exprsym3_list_r '=' LRANGE expr
	{
		$$ = nod(ORANGE, $1, $4);
		$$->etype = 0;	// := flag
		// ...
	}

orange_stmt は、osimple_stmt(おそらくオプションのシンプルなステートメント)または exprsym3_list_r '=' LRANGE expr のいずれかとして定義されていました。後者の規則は for key, value := range collection のような range 構文に対応します。

変更後:

range_stmt:
	exprsym3_list_r '=' LRANGE expr
	{
		$$ = nod(ORANGE, $1, $4);
		$$->etype = 0;	// := flag
		// ...
	}
  • orange_stmt という非終端記号が range_stmt に改名されました。これは、その役割をより明確にするための命名規則の改善です。
  • osimple_stmt の選択肢が range_stmt から削除されました。これにより、range_stmt は純粋に for...range 構文の右辺(key, value := range collection の部分)のみを表現するようになりました。これは、range 構文が for ループのヘッダー内でしか意味を持たないため、より厳密な文法定義になったと言えます。

アクションブロック $$ = nod(ORANGE, $1, $4); は、ORANGE という種類のASTノードを作成し、その子ノードとして $1exprsym3_list_r、つまり key, value の部分)と $4expr、つまり collection の部分)を設定しています。$$->etype = 0; // := flag は、おそらく := 演算子による変数宣言が行われたことを示すフラグを設定しています。

2. for_header 規則の簡素化と range_stmt の統合

for_header は、for ループの括弧内の部分(例: init; condition; postrange collection)を解析する規則です。

変更前:

for_header:
	osimple_stmt ';' orange_stmt ';' osimple_stmt
	{
		// ... init ; test ; incr ...
		if($3 != N && $3->op == ORANGE) {
			$$ = dorange($3);
			$$->ninit = list($$->ninit, $1);
			$$->nincr = list($$->nincr, $5);
			break;
		}
		// ... normal test ...
	}
|	orange_stmt
	{
		// range
		if($1 != N && $1->op == ORANGE) {
			$$ = dorange($1);
			break;
		}
		// normal test
		// ...
	}

変更前の for_header は、for init; test; incr 形式と for range 形式の両方を同じ規則内で処理しようとしていました。特に、osimple_stmt ';' orange_stmt ';' osimple_stmt の規則内で、$3ORANGE オペレーションを持つ場合に range ループとして特別扱いするロジックが含まれていました。これは、文法規則とセマンティックアクションが密結合しており、読みにくい構造でした。

変更後:

for_header:
	osimple_stmt ';' osimple_stmt ';' osimple_stmt
	{
		// init ; test ; incr
		if($5 != N && $5->colas != 0)
			yyerror("cannot declare in the for-increment");
		$$ = nod(OFOR, N, N);
		$$->ninit = $1;
		$$->ntest = $3;
		$$->nincr = $5;
	}
|	osimple_stmt
	{
		// normal test
		$$ = nod(OFOR, N, N);
		$$->ninit = N;
		$$->ntest = $1;
		$$->nincr = N;
	}
|	range_stmt
	{
		$$ = dorange($1);
		addtotop($$);
	}
  • for_header の最初の規則 osimple_stmt ';' osimple_stmt ';' osimple_stmt は、純粋に for init; test; incr 形式のループを処理するように簡素化されました。以前の if($3 != N && $3->op == ORANGE) のような range 構文の特殊処理が削除されました。これにより、この規則は通常の for ループの解析に特化し、より明確になりました。
  • for_header の2番目の規則 osimple_stmt は、for condition 形式のループを処理します。以前の orange_stmt を含む複雑なロジックが削除され、シンプルな条件式のみのループに特化しました。
  • 新しい規則 range_stmtfor_header に追加されました。これにより、for...range 構文は独立した規則として明確に定義され、解析ロジックが分離されました。
    • $$ = dorange($1);: range_stmt から受け取ったASTノード($1)を dorange 関数で処理し、最終的な for ループのASTノードを構築します。
    • addtotop($$);: 生成された for...range ループのASTノードを、現在のスコープのトップレベルに追加します。これは、for...range ループが新しいスコープを導入する可能性があるため、そのスコープ内で宣言された変数を適切に処理するために必要です。

変更の意図と効果

この変更は、Go言語の for ループ、特に for...range 構文の解析ロジックを大幅に改善しています。

  • 文法の明確化: range_stmtfor...range 構文の右辺のみを表現するように特化され、for_header から range 構文の特殊処理が分離されたことで、文法規則がより明確で理解しやすくなりました。
  • 関心の分離: 各文法規則が特定の種類の for ループに特化することで、コードの関心事が分離され、保守性が向上しました。
  • エラーハンドリングの改善: yyerror("cannot declare in the for-increment") のようなエラーチェックが、適切な文法規則のコンテキストで行われるようになりました。
  • 将来の拡張性: 文法が整理されたことで、将来的に for ループや range 構文に新しい機能が追加された場合でも、既存のコードに大きな影響を与えることなく変更を加えやすくなります。

このコミットは、Go言語のコンパイラが初期段階でどのように進化し、文法定義が洗練されていったかを示す良い例です。

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

diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y
index 4aafd0b0c1..d04991dc47 100644
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -52,7 +52,7 @@
 %type	<node>		Astmt Bstmt
 %type	<node>		for_stmt for_body for_header
 %type	<node>		if_stmt if_body if_header select_stmt
-%type	<node>		simple_stmt osimple_stmt orange_stmt semi_stmt
+%type	<node>		simple_stmt osimple_stmt range_stmt semi_stmt
 %type	<node>		expr uexpr pexpr expr_list oexpr oexpr_list expr_list_r
 %type	<node>		exprsym3_list_r exprsym3
 %type	<node>		name onew_name new_name new_name_list_r new_field
@@ -536,9 +536,8 @@ compound_stmt:
 		popdcl();
 	}
 
-orange_stmt:
-	osimple_stmt
-|	exprsym3_list_r '=' LRANGE expr
+range_stmt:
+	exprsym3_list_r '=' LRANGE expr
 	{
 		$$ = nod(ORANGE, $1, $4);
 		$$->etype = 0;	// := flag
@@ -550,14 +549,8 @@ orange_stmt:
 	}
 
 for_header:
-\tosimple_stmt ';' orange_stmt ';' osimple_stmt
+\tosimple_stmt ';' osimple_stmt ';' osimple_stmt
 	{
-\t\tif($3 != N && $3->op == ORANGE) {\n-\t\t\t$$ = dorange($3);\n-\t\t\t$$->ninit = list($$->ninit, $1);\n-\t\t\t$$->nincr = list($$->nincr, $5);\n-\t\t\tbreak;\n-\t\t}\n \t\t// init ; test ; incr
 \t\tif($5 != N && $5->colas != 0)
 \t\t\tyyerror("cannot declare in the for-increment");
 \t\t$$ = nod(OFOR, N, N);
@@ -566,19 +559,19 @@ for_header:
 \t\t$$->ntest = $3;
 \t\t$$->nincr = $5;
 	}
-|\torange_stmt
+|\tosimple_stmt
 	{
-\t\t// range
-\t\tif($1 != N && $1->op == ORANGE) {\n-\t\t\t$$ = dorange($1);\n-\t\t\tbreak;\n-\t\t}\n \t\t// normal test
 \t\t$$ = nod(OFOR, N, N);
 \t\t$$->ninit = N;
 \t\t$$->ntest = $1;
 \t\t$$->nincr = N;
 	}
+|\trange_stmt
+\t{\n+\t\t$$ = dorange($1);\n+\t\taddtotop($$);\n+\t}\
 
 for_body:
 	for_header compound_stmt

コアとなるコードの解説

1. %type 宣言の変更

- %type	<node>		simple_stmt osimple_stmt orange_stmt semi_stmt
+ %type	<node>		simple_stmt osimple_stmt range_stmt semi_stmt
  • 変更内容: orange_stmt という非終端記号の型宣言が range_stmt に変更されました。
  • 解説: これは、orange_stmtrange_stmt に改名されたことに伴う、Yacc/Bisonの型宣言の更新です。これにより、パーサーが range_stmt を処理する際に、それが node 型の値を返すことを認識します。

2. orange_stmt 規則の削除と range_stmt 規則の追加

-orange_stmt:
-	osimple_stmt
-|	exprsym3_list_r '=' LRANGE expr
+range_stmt:
+	exprsym3_list_r '=' LRANGE expr
  • 変更内容: orange_stmt という名前の規則が削除され、代わりに range_stmt という新しい名前の規則が追加されました。また、orange_stmt にあった osimple_stmt という選択肢が range_stmt から削除されました。
  • 解説:
    • 命名の明確化: orange_stmtrange 構文を指す非公式な名前だったと考えられます。これを range_stmt に変更することで、その役割がより直感的に理解できるようになりました。
    • 文法の厳密化: 以前の orange_stmtosimple_stmt(おそらく単なる式や代入文など)も許容していましたが、range_stmtexprsym3_list_r '=' LRANGE expr という for...range 構文の右辺に特化しました。これは、range 構文が for ループのコンテキストでのみ意味を持つため、文法をより厳密に定義し、不適切な使用を早期に検出できるようにするための変更です。
    • アクションブロック: 規則に続く { ... } 内のコードは、この文法規則がマッチしたときに実行されるアクションです。
      • $$ = nod(ORANGE, $1, $4);: ORANGE というオペレーションコードを持つASTノードを作成し、そのノードに $1exprsym3_list_r、つまり key, value の部分)と $4expr、つまり collection の部分)を子ノードとして関連付けています。$$ はこの規則の左辺(range_stmt)が返すASTノードです。
      • $$->etype = 0; // := flag: 生成されたASTノードの etype フィールドを 0 に設定しています。コメントから、これは := 演算子による変数宣言が行われたことを示すフラグであると推測されます。

3. for_header 規則の変更

for_headerfor ループのヘッダー部分を解析する規則です。この部分が最も大きく変更されています。

3.1. 最初の規則の変更(for init; test; incr 形式)

 for_header:
-\tosimple_stmt ';' orange_stmt ';' osimple_stmt
+\tosimple_stmt ';' osimple_stmt ';' osimple_stmt
 	{
-\t\tif($3 != N && $3->op == ORANGE) {\n-\t\t\t$$ = dorange($3);\n-\t\t\t$$->ninit = list($$->ninit, $1);\n-\t\t\t$$->nincr = list($$->nincr, $5);\n-\t\t\tbreak;\n-\t\t}\n \t\t// init ; test ; incr
 \t\tif($5 != N && $5->colas != 0)
 \t\t\tyyerror("cannot declare in the for-increment");
 \t\t$$ = nod(OFOR, N, N);
 \t\t$$->ninit = $1;
 \t\t$$->ntest = $3;
 \t\t$$->nincr = $5;
 	}
  • 変更内容:
    • osimple_stmt ';' orange_stmt ';' osimple_stmtosimple_stmt ';' osimple_stmt ';' osimple_stmt に変更されました。つまり、真ん中の orange_stmtosimple_stmt に置き換えられました。
    • それに伴い、アクションブロック内の if($3 != N && $3->op == ORANGE) { ... } という range 構文を特別扱いするロジックが削除されました。
  • 解説:
    • 関心の分離: 以前は、この規則が通常の for init; test; incr 形式と、for ループの test 部分に range 構文が誤って現れた場合の両方を処理しようとしていました。この変更により、この規則は純粋に for init; test; incr 形式のループに特化しました。
    • $1 は初期化ステートメント、$3 は条件式、$5 はインクリメントステートメントに対応します。
    • if($5 != N && $5->colas != 0) yyerror("cannot declare in the for-increment");: これは、for ループのインクリメント部分で新しい変数を宣言しようとした場合にエラーを報告するチェックです。Go言語では、for ループのインクリメント部分で := を使って新しい変数を宣言することはできません。
    • $$ = nod(OFOR, N, N);: OFOR(通常の for ループを表すオペレーションコード)を持つASTノードを作成します。
    • $$->ninit = $1; $$->ntest = $3; $$->nincr = $5;: 生成された for ループのASTノードの ninit(初期化)、ntest(条件)、nincr(インクリメント)フィールドに、それぞれ対応する子ノードを設定しています。

3.2. 2番目の規則の変更(for condition 形式)

-|\torange_stmt
+|\tosimple_stmt
 	{
-\t\t// range
-\t\tif($1 != N && $1->op == ORANGE) {\n-\t\t\t$$ = dorange($1);\n-\t\t\tbreak;\n-\t\t}\n \t\t// normal test
 \t\t$$ = nod(OFOR, N, N);
 \t\t$$->ninit = N;
 \t\t$$->ntest = $1;
 \t\t$$->nincr = N;
 	}
  • 変更内容: orange_stmtosimple_stmt に変更されました。それに伴い、アクションブロック内の if($1 != N && $1->op == ORANGE) { ... } という range 構文を特別扱いするロジックが削除されました。
  • 解説: 以前は、この規則も for condition 形式と for range 形式の両方を処理しようとしていました。この変更により、この規則は純粋に for condition 形式のループに特化しました。
    • $1 は条件式に対応します。
    • $$ = nod(OFOR, N, N);: OFOR ノードを作成します。
    • $$->ninit = N; $$->ntest = $1; $$->nincr = N;: 初期化とインクリメントがないため、それらのフィールドは N(nil)に設定され、ntest フィールドに条件式が設定されます。

3.3. 新しい規則の追加(for...range 形式)

+|\trange_stmt
+\t{\n+\t\t$$ = dorange($1);\n+\t\taddtotop($$);\n+\t}\
  • 変更内容: range_stmt という新しい規則が for_header に追加されました。
  • 解説:
    • for...range の独立した処理: この新しい規則は、for...range ループのヘッダーを明示的に処理するために導入されました。これにより、for...range 構文の解析が他の for ループの形式から完全に分離され、文法がよりモジュール化されました。
    • $1 は、先に定義された range_stmt 規則から返されたASTノード(for...range の右辺に対応)です。
    • $$ = dorange($1);: dorange 関数は、range 構文のASTノードを受け取り、それをGoコンパイラの内部表現に適した for ループのAST構造に変換する役割を担います。これは、for...range ループが内部的には通常の for ループに変換される(例えば、イテレータの初期化、条件チェック、次の要素への移動など)ため、その変換処理を行う関数です。
    • addtotop($$);: 生成された for...range ループのASTノードを、現在のスコープのトップレベルに追加します。これは、for...range ループが新しいスコープを導入し、その中でループ変数(key, value)が宣言されるため、それらの変数を適切にスコープ管理するために必要です。

これらの変更全体として、Goコンパイラの for ループ解析のロジックが大幅に整理され、各 for ループの形式がより明確かつ独立して処理されるようになりました。これにより、コンパイラのコードベースの可読性、保守性、そして将来の拡張性が向上しています。

関連リンク

参考にした情報源リンク

  • Go言語の for ループに関する公式ドキュメントやチュートリアル
  • Yacc/Bisonの文法定義とアクションに関する一般的な情報
  • Go言語の初期のコミット履歴と開発に関する議論(公開されている場合)
  • Go言語のコンパイラ設計に関する一般的な知識# [インデックス 1581] ファイルの概要

このコミットは、Go言語のコンパイラの一部である src/cmd/gc/go.y ファイルに対する変更です。go.y は、Go言語の構文解析器(パーサー)を生成するためのYacc(Yet Another Compiler-Compiler)またはBisonの文法定義ファイルです。Goコンパイラは、このファイルに記述された文法規則に基づいてGoのソースコードを解析し、抽象構文木(AST)を構築します。具体的には、for ループ、特に for...range 構文の解析に関連する文法規則が変更されています。

コミット

このコミットは、Go言語のコンパイラにおける range 構文の文法定義を整理し、クリーンアップすることを目的としています。具体的には、orange_stmt という非終端記号を range_stmt に改名し、for ループのヘッダー部分の文法規則を簡素化しています。これにより、コンパイラのコードベースの可読性と保守性が向上し、将来的な言語機能の拡張に備えるものと考えられます。

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

https://github.com/golang/go/commit/3ec4675220f18c3bc6680e71c6ce09a76b641c8c

元コミット内容

commit 3ec4675220f18c3bc6680e71c6ce09a76b641c8c
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jan 28 15:41:50 2009 -0800

    clean up range grammar
    
    R=ken
    OCL=23712
    CL=23714
---
 src/cmd/gc/go.y | 27 ++++++++++-----------------
 1 file changed, 10 insertions(+), 17 deletions(-)\n
diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y
index 4aafd0b0c1..d04991dc47 100644
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -52,7 +52,7 @@
 %type	<node>		Astmt Bstmt
 %type	<node>		for_stmt for_body for_header
 %type	<node>		if_stmt if_body if_header select_stmt
-%type	<node>		simple_stmt osimple_stmt orange_stmt semi_stmt
+%type	<node>		simple_stmt osimple_stmt range_stmt semi_stmt
 %type	<node>		expr uexpr pexpr expr_list oexpr oexpr_list expr_list_r
 %type	<node>		exprsym3_list_r exprsym3
 %type	<node>		name onew_name new_name new_name_list_r new_field
@@ -536,9 +536,8 @@ compound_stmt:
 		popdcl();
 	}
 
-orange_stmt:
-	osimple_stmt
-|	exprsym3_list_r '=' LRANGE expr
+range_stmt:
+	exprsym3_list_r '=' LRANGE expr
 	{
 		$$ = nod(ORANGE, $1, $4);
 		$$->etype = 0;	// := flag
@@ -550,14 +549,8 @@ orange_stmt:
 	}
 
 for_header:
-\tosimple_stmt ';' orange_stmt ';' osimple_stmt
+\tosimple_stmt ';' osimple_stmt ';' osimple_stmt
 	{
-\t\tif($3 != N && $3->op == ORANGE) {\n-\t\t\t$$ = dorange($3);\n-\t\t\t$$->ninit = list($$->ninit, $1);\n-\t\t\t$$->nincr = list($$->nincr, $5);\n-\t\t\tbreak;\n-\t\t}\n \t\t// init ; test ; incr
 \t\tif($5 != N && $5->colas != 0)
 \t\t\tyyerror("cannot declare in the for-increment");
 \t\t$$ = nod(OFOR, N, N);
@@ -566,19 +559,19 @@ for_header:
 \t\t$$->ntest = $3;
 \t\t$$->nincr = $5;
 	}
-|\torange_stmt
+|\tosimple_stmt
 	{
-\t\t// range
-\t\tif($1 != N && $1->op == ORANGE) {\n-\t\t\t$$ = dorange($1);\n-\t\t\tbreak;\n-\t\t}\n \t\t// normal test
 \t\t$$ = nod(OFOR, N, N);
 \t\t$$->ninit = N;
 \t\t$$->ntest = $1;
 \t\t$$->nincr = N;
 	}
+|\trange_stmt
+\t{\n+\t\t$$ = dorange($1);\n+\t\taddtotop($$);\n+\t}\
 
 for_body:
 	for_header compound_stmt

変更の背景

このコミットが行われた2009年1月は、Go言語がまだ一般に公開される前の非常に初期の開発段階でした。Go言語の設計と実装は活発に進められており、文法や内部構造は頻繁に変更されていました。Go言語は2009年11月にオープンソースプロジェクトとして公開されましたが、その設計は2007年からGoogle社内で進められていました。この時期は、言語のコア機能が固まりつつある段階であり、コンパイラの内部構造も試行錯誤が繰り返されていました。

この「clean up range grammar」というコミットメッセージは、for...range ループの文法解析に関する既存の実装が、何らかの理由で最適ではない、あるいは将来の拡張性を考慮すると問題があると考えられたことを示唆しています。具体的な背景としては、以下のような点が考えられます。

  1. 文法の整理と簡素化: orange_stmt という名前は、range 構文を直接的に示しておらず、その役割が不明瞭でした。より分かりやすい range_stmt への改名が行われたのは、コードの意図を明確にするためです。また、for_header 内での orange_stmt の扱いが冗長であったり、他の for ループの形式と統合されていなかったりしたため、文法規則を整理し、より一貫性のある解析ロジックを目指したと考えられます。初期の言語開発では、機能が追加されるたびに文法が複雑になりがちであり、定期的な整理は不可欠です。
  2. コードの可読性と保守性の向上: コンパイラのコードベースは非常に複雑になりがちです。文法定義をクリーンアップすることで、将来の機能追加やバグ修正が容易になります。特に、Yacc/Bisonのようなパーサー生成ツールを使用する場合、文法規則の構造が直接的に生成されるパーサーのコードの複雑さに影響するため、簡潔で明確な文法は重要です。
  3. 解析効率の改善: 文法規則の整理は、パーサーの効率をわずかに向上させる可能性もあります。特に、曖昧な規則や重複する規則を排除することで、解析時のバックトラックを減らし、パーサーのパフォーマンスを向上させることができます。これは、大規模なコードベースを扱うコンパイラにとって重要な要素です。
  4. 言語仕様の進化への対応: Go言語の for...range 構文は、スライス、配列、文字列、マップ、チャネルなど、様々なデータ構造をイテレートするために使用されます。初期の段階では、これらのイテレーションのセマンティクスがまだ完全に固まっていなかった可能性があり、文法を整理することで、将来の言語仕様の変更に柔軟に対応できるようにしたと考えられます。Go言語は、シンプルさと効率性を重視して設計されており、その設計思想はコンパイラの内部構造にも反映されています。

要するに、この変更は、Go言語の for...range 構文の解析をより堅牢で、理解しやすく、保守しやすいものにするための、初期段階における重要なリファクタリングの一環であったと言えます。これは、Go言語がその後の安定したリリースに向けて、基盤を固めていく過程で不可欠なステップでした。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

1. Yacc/Bisonと文法定義

go.y ファイルは、Yacc(Yet Another Compiler-Compiler)またはそのGNU版であるBisonの入力ファイルです。Yacc/Bisonは、BNF(Backus-Naur Form)に似た形式で記述された文法規則から、C言語(または他の言語)の構文解析器(パーサー)を自動生成するツールです。Goコンパイラの初期バージョンでは、このアプローチが採用されていました。

  • 文法規則(Grammar Rules): プログラミング言語の構文を定義する一連の規則です。例えば、「式は項の後に演算子と項が続く」といった形です。各規則は、非終端記号がどのように終端記号や他の非終端記号の組み合わせで構成されるかを示します。
  • 非終端記号(Non-terminal Symbols): 文法規則の左辺に現れる記号で、さらに別の規則によって展開される記号です。これらは言語の抽象的な構文要素を表します。例: expression, statement, for_header
  • 終端記号(Terminal Symbols): 文法規則の右辺に現れる記号で、これ以上展開されない記号です。これらは字句解析器(レキサー)によって識別される最小単位のトークンに対応します。例: ID (識別子), NUMBER (数値), '+', ';', LRANGE (Goの range キーワードに対応するトークン)。
  • アクション(Actions): 各文法規則の右辺に付随するC言語のコードブロックです。パーサーがその規則にマッチしたときに実行されます。通常、抽象構文木(AST)のノードを構築したり、セマンティックチェックを行ったりします。
    • $$: 現在の規則の左辺の非終端記号に対応する値(通常はASTノードへのポインタ)を表します。この値は、この規則が解析した構文要素のセマンティックな情報を保持します。
    • $1, $2, ...: 現在の規則の右辺の記号に対応する値(ASTノードへのポインタなど)を表します。$1 は右辺の最初の記号、$2 は2番目の記号、といった具合です。これらの値は、子要素のセマンティックな情報を親要素に渡すために使用されます。
  • %type <node>: Yacc/Bisonのディレクティブで、特定の非終端記号がどのような型の値を保持するかを宣言します。ここでは、node 型(おそらくASTノードを表す構造体へのポインタ)を保持することを示しています。これは、パーサーが構文解析中に構築するASTのノードの型を定義するために重要です。

2. Go言語の for ループと for...range

Go言語には、いくつかの for ループの形式があります。これらの形式は、それぞれ異なるイテレーションのパターンに対応しています。

  • 通常の for ループ: C言語やJavaに似た形式で、初期化ステートメント、条件式、後処理ステートメントを持ちます。
    for init; condition; post {
        // ...
    }
    
    init はループ開始前に一度だけ実行され、condition は各イテレーションの前に評価され、post は各イテレーションの後に実行されます。
  • 条件式のみの for ループ:
    for condition {
        // ...
    }
    
    これは while ループに相当します。conditiontrue である限りループが続行されます。
  • 無限ループ:
    for {
        // ...
    }
    
    条件が指定されていないため、明示的な break ステートメントがない限り無限に実行されます。
  • for...range ループ: 配列、スライス、文字列、マップ、チャネルなどの要素をイテレートするために使用されます。これはGo言語の特徴的なループ形式であり、コレクションの要素を簡潔に処理できます。
    for key, value := range collection {
        // ...
    }
    
    または、インデックス/キーが不要な場合:
    for _, value := range collection {
        // ...
    }
    
    または、値が不要な場合:
    for key := range collection {
        // ...
    }
    
    このコミットは、特に for...range 構文の解析に関連しています。for...range は、内部的には通常の for ループに変換されることが多く、その変換ロジックがコンパイラ内で実装されます。

3. Goコンパイラの内部構造(初期段階)

Goコンパイラは、ソースコードを機械語に変換する過程で、以下の主要なフェーズを経ます。このコミットは、特に構文解析フェーズに焦点を当てています。

  1. 字句解析(Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。このフェーズはレキサー(またはスキャナー)によって行われます。
  2. 構文解析(Syntax Analysis): トークンのストリームを文法規則に基づいて解析し、抽象構文木(AST)を構築します。go.y がこのフェーズで使用され、パーサーが生成されます。ASTは、プログラムの構造を階層的に表現したデータ構造であり、後続のコンパイルフェーズの入力となります。
  3. 意味解析(Semantic Analysis): ASTに対して型チェック、名前解決、スコープチェックなどの意味的な検証を行います。このフェーズでは、プログラムが言語のセマンティックルールに準拠しているかを確認します。
  4. 中間コード生成(Intermediate Code Generation): ASTをより低レベルの中間表現に変換します。この中間表現は、特定のCPUアーキテクチャに依存しない形でプログラムを表現します。
  5. 最適化(Optimization): 中間コードを最適化し、実行効率を向上させます。
  6. コード生成(Code Generation): 最終的な機械語コードを生成します。

このコミットは、構文解析フェーズにおける for...range 構文のAST構築ロジックに影響を与えます。

  • nod(OP, Left, Right): ASTノードを作成する関数。OP はノードの種類(例: ORANGEfor...range ループを表す内部オペレーションコード)、LeftRight はそのノードの子ノードです。これらのノードは、プログラムの構造を表現するために使用されます。
  • $$->ninit, $$->ntest, $$->nincr: for ループのASTノードのフィールドで、それぞれ初期化ステートメント、条件式、インクリメントステートメントに対応する子ノードを格納します。これらのフィールドは、通常の for ループの三つの部分を表現するために使用されます。
  • dorange($1): range 構文のASTノードを処理し、適切な for ループのAST構造に変換する関数。これは、for...range ループが内部的に通常の for ループに展開される際の変換ロジックの一部です。
  • addtotop($$): 生成されたASTノードを、現在のスコープのトップレベルに追加する関数。これは、特に新しいスコープを導入する構文(例: for...range ループ内で宣言される変数)の管理に重要です。
  • list(list_head, new_node): ASTノードのリストに新しいノードを追加する関数。これは、複数のステートメントや式をリストとして管理するために使用されます。
  • N: nil または null に相当する、ノードが存在しないことを示す定数。
  • yyerror("message"): 構文エラーを報告する関数。パーサーが文法規則に違反する入力を見つけた場合に呼び出されます。

技術的詳細

このコミットの主要な変更点は、go.y ファイルにおける range 構文の文法規則の再定義と、それに伴う for_header 規則の簡素化です。これらの変更は、Go言語の for...range ループの解析をより構造化され、明確なものにすることを目的としています。

1. orange_stmt から range_stmt への改名と定義の変更

変更前:

orange_stmt:
	osimple_stmt
|	exprsym3_list_r '=' LRANGE expr
	{
		$$ = nod(ORANGE, $1, $4);
		$$->etype = 0;	// := flag
		// ... その他のアクション ...
	}

変更前の orange_stmt は、osimple_stmt(オプションのシンプルなステートメント)または exprsym3_list_r '=' LRANGE expr のいずれかとして定義されていました。後者の規則は for key, value := range collection のような range 構文の右辺に対応します。osimple_stmt が含まれていたことで、orange_stmt の意味が曖昧になっていました。

変更後:

range_stmt:
	exprsym3_list_r '=' LRANGE expr
	{
		$$ = nod(ORANGE, $1, $4);
		$$->etype = 0;	// := flag
		// ... その他のアクション ...
	}
  • 非終端記号の改名: orange_stmt という非終端記号が range_stmt に改名されました。これは、その役割をより明確にするための命名規則の改善です。range_stmt という名前は、Go言語の range キーワードと直接的に関連付けられ、その意味が直感的に理解できるようになりました。
  • 定義の厳密化: osimple_stmt の選択肢が range_stmt から削除されました。これにより、range_stmt は純粋に for...range 構文の右辺(key, value := range collection の部分)のみを表現するようになりました。これは、range 構文が for ループのヘッダー内でしか意味を持たないため、より厳密な文法定義になったと言えます。この厳密化により、パーサーは range 構文をより正確に識別し、誤った文法構造を早期に検出できるようになります。

アクションブロック $$ = nod(ORANGE, $1, $4); は、ORANGE という種類のASTノードを作成し、その子ノードとして $1exprsym3_list_r、つまり key, value の部分)と $4expr、つまり collection の部分)を設定しています。$$->etype = 0; // := flag は、おそらく := 演算子による変数宣言が行われたことを示すフラグを設定しています。これは、for...range ループで新しい変数を宣言する際のセマンティックな情報をASTに付加するために使用されます。

2. for_header 規則の簡素化と range_stmt の統合

for_header は、for ループの括弧内の部分(例: init; condition; postrange collection)を解析する規則です。この部分の変更は、for ループの異なる形式の解析ロジックを分離し、明確化することを目的としています。

変更前:

for_header:
	osimple_stmt ';' orange_stmt ';' osimple_stmt
	{
		// ... init ; test ; incr ...
		if($3 != N && $3->op == ORANGE) {
			$$ = dorange($3);
			$$->ninit = list($$->ninit, $1);
			$$->nincr = list($$->nincr, $5);
			break;
		}
		// ... normal test ...
	}
|	orange_stmt
	{
		// range
		if($1 != N && $1->op == ORANGE) {
			$$ = dorange($1);
			break;
		}
		// normal test
		// ...
	}

変更前の for_header は、for init; test; incr 形式と for range 形式の両方を同じ規則内で処理しようとしていました。特に、osimple_stmt ';' orange_stmt ';' osimple_stmt の規則内で、$3ORANGE オペレーションを持つ場合に range ループとして特別扱いするロジックが含まれていました。これは、文法規則とセマンティックアクションが密結合しており、読みにくい構造でした。また、orange_stmt が単独で for_header になる規則も存在し、ここでも range 構文の特殊処理が行われていました。

変更後:

for_header:
	osimple_stmt ';' osimple_stmt ';' osimple_stmt
	{
		// init ; test ; incr
		if($5 != N && $5->colas != 0)
			yyerror("cannot declare in the for-increment");
		$$ = nod(OFOR, N, N);
		$$->ninit = $1;
		$$->ntest = $3;
		$$->nincr = $5;
	}
|	osimple_stmt
	{
		// normal test
		$$ = nod(OFOR, N, N);
		$$->ninit = N;
		$$->ntest = $1;
		$$->nincr = N;
	}
|	range_stmt
	{
		$$ = dorange($1);
		addtotop($$);
	}
  • 最初の規則の簡素化: for_header の最初の規則 osimple_stmt ';' osimple_stmt ';' osimple_stmt は、純粋に for init; test; incr 形式のループを処理するように簡素化されました。以前の if($3 != N && $3->op == ORANGE) のような range 構文の特殊処理が削除されました。これにより、この規則は通常の for ループの解析に特化し、より明確になりました。

    • $1 は初期化ステートメント、$3 は条件式、$5 はインクリメントステートメントに対応します。
    • if($5 != N && $5->colas != 0) yyerror("cannot declare in the for-increment");: これは、Go言語の仕様で許可されていない、for ループのインクリメント部分での新しい変数宣言(:= を使用)を検出するためのエラーチェックです。
    • $$ = nod(OFOR, N, N);: OFOR(通常の for ループを表すオペレーションコード)を持つASTノードを作成します。
    • $$->ninit = $1; $$->ntest = $3; $$->nincr = $5;: 生成された for ループのASTノードの ninit(初期化)、ntest(条件)、nincr(インクリメント)フィールドに、それぞれ対応する子ノードを設定しています。
  • 2番目の規則の簡素化: for_header の2番目の規則 osimple_stmt は、for condition 形式のループを処理します。以前の orange_stmt を含む複雑なロジックが削除され、シンプルな条件式のみのループに特化しました。

    • $1 は条件式に対応します。
    • $$ = nod(OFOR, N, N);: OFOR ノードを作成します。
    • $$->ninit = N; $$->ntest = $1; $$->nincr = N;: 初期化とインクリメントがないため、それらのフィールドは N(nil)に設定され、ntest フィールドに条件式が設定されます。
  • 新しい range_stmt 規則の追加: 新しい規則 range_stmtfor_header に追加されました。これにより、for...range 構文は独立した規則として明確に定義され、解析ロジックが分離されました。

    • $1 は、先に定義された range_stmt 規則から返されたASTノード(for...range の右辺に対応)です。
    • $$ = dorange($1);: dorange 関数は、range_stmt から受け取ったASTノード($1)を処理し、最終的な for ループのASTノードを構築する役割を担います。これは、for...range ループが内部的には通常の for ループに変換される(例えば、イテレータの初期化、条件チェック、次の要素への移動など)ため、その変換処理を行う関数です。
    • addtotop($$);: 生成された for...range ループのASTノードを、現在のスコープのトップレベルに追加します。これは、for...range ループが新しいスコープを導入し、その中でループ変数(key, value)が宣言されるため、それらの変数を適切にスコープ管理するために必要です。

変更の意図と効果

この変更は、Go言語の for ループ、特に for...range 構文の解析ロジックを大幅に改善しています。

  • 文法の明確化: range_stmtfor...range 構文の右辺のみを表現するように特化され、for_header から range 構文の特殊処理が分離されたことで、文法規則がより明確で理解しやすくなりました。これにより、パーサーの動作が予測しやすくなり、デバッグが容易になります。
  • 関心の分離: 各文法規則が特定の種類の for ループに特化することで、コードの関心事が分離され、保守性が向上しました。これは、ソフトウェア設計における「単一責任の原則」に合致するもので、各モジュールが特定の機能に集中することで、変更の影響範囲を限定できます。
  • エラーハンドリングの改善: yyerror("cannot declare in the for-increment") のようなエラーチェックが、適切な文法規則のコンテキストで行われるようになりました。これにより、より正確で分かりやすいエラーメッセージをユーザーに提供できるようになります。
  • 将来の拡張性: 文法が整理されたことで、将来的に for ループや range 構文に新しい機能が追加された場合でも、既存のコードに大きな影響を与えることなく変更を加えやすくなります。モジュール化された設計は、新しい機能の追加や既存機能の変更を容易にします。

このコミットは、Go言語のコンパイラが初期段階でどのように進化し、文法定義が洗練されていったかを示す良い例です。このような基盤の整備が、Go言語のその後の安定性と成長に貢献しました。

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

diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y
index 4aafd0b0c1..d04991dc47 100644
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -52,7 +52,7 @@
 %type	<node>		Astmt Bstmt
 %type	<node>		for_stmt for_body for_header
 %type	<node>		if_stmt if_body if_header select_stmt
-%type	<node>		simple_stmt osimple_stmt orange_stmt semi_stmt
+%type	<node>		simple_stmt osimple_stmt range_stmt semi_stmt
 %type	<node>		expr uexpr pexpr expr_list oexpr oexpr_list expr_list_r
 %type	<node>		exprsym3_list_r exprsym3
 %type	<node>		name onew_name new_name new_name_list_r new_field
@@ -536,9 +536,8 @@ compound_stmt:
 		popdcl();
 	}
 
-orange_stmt:
-	osimple_stmt
-|	exprsym3_list_r '=' LRANGE expr
+range_stmt:
+	exprsym3_list_r '=' LRANGE expr
 	{
 		$$ = nod(ORANGE, $1, $4);
 		$$->etype = 0;	// := flag
@@ -550,14 +549,8 @@ orange_stmt:
 	}
 
 for_header:
-\tosimple_stmt ';' orange_stmt ';' osimple_stmt
+\tosimple_stmt ';' osimple_stmt ';' osimple_stmt
 	{
-\t\tif($3 != N && $3->op == ORANGE) {\n-\t\t\t$$ = dorange($3);\n-\t\t\t$$->ninit = list($$->ninit, $1);\n-\t\t\t$$->nincr = list($$->nincr, $5);\n-\t\t\tbreak;\n-\t\t}\n \t\t// init ; test ; incr
 \t\tif($5 != N && $5->colas != 0)
 \t\t\tyyerror("cannot declare in the for-increment");
 \t\t$$ = nod(OFOR, N, N);
@@ -566,19 +559,19 @@ for_header:
 \t\t$$->ntest = $3;
 \t\t$$->nincr = $5;
 	}
-|\torange_stmt
+|\tosimple_stmt
 	{
-\t\t// range
-\t\tif($1 != N && $1->op == ORANGE) {\n-\t\t\t$$ = dorange($1);\n-\t\t\tbreak;\n-\t\t}\n \t\t// normal test
 \t\t$$ = nod(OFOR, N, N);
 \t\t$$->ninit = N;
 \t\t$$->ntest = $1;
 \t\t$$->nincr = N;
 	}
+|\trange_stmt
+\t{\n+\t\t$$ = dorange($1);\n+\t\taddtotop($$);\n+\t}\
 
 for_body:
 	for_header compound_stmt

コアとなるコードの解説

1. %type 宣言の変更

- %type	<node>		simple_stmt osimple_stmt orange_stmt semi_stmt
+ %type	<node>		simple_stmt osimple_stmt range_stmt semi_stmt
  • 変更内容: orange_stmt という非終端記号の型宣言が range_stmt に変更されました。
  • 解説: Yacc/Bisonの文法ファイルでは、%type ディレクティブを使用して、非終端記号が保持するセマンティック値の型を宣言します。この変更は、orange_stmtrange_stmt に改名されたことに伴う、型宣言の更新です。これにより、パーサーが range_stmt を処理する際に、それが node 型(おそらく抽象構文木(AST)のノードを表す構造体へのポインタ)の値を返すことを認識し、型安全なAST構築を保証します。

2. orange_stmt 規則の削除と range_stmt 規則の追加

-orange_stmt:
-	osimple_stmt
-|	exprsym3_list_r '=' LRANGE expr
+range_stmt:
+	exprsym3_list_r '=' LRANGE expr
  • 変更内容: orange_stmt という名前の規則が削除され、代わりに range_stmt という新しい名前の規則が追加されました。また、orange_stmt にあった osimple_stmt という選択肢が range_stmt から削除されました。
  • 解説:
    • 命名の明確化: 以前の orange_stmt は、その名前から直接的に range 構文を連想しにくいものでした。これを range_stmt に変更することで、その役割がより直感的に理解できるようになりました。これは、コードの可読性と保守性を向上させるための重要なリファクタリングです。
    • 文法の厳密化: 以前の orange_stmtosimple_stmt(おそらく単なる式や代入文など)も許容していましたが、range_stmtexprsym3_list_r '=' LRANGE expr という for...range 構文の右辺に特化しました。Go言語の range 構文は for ループのコンテキストでのみ意味を持つため、この変更により文法がより厳密に定義され、不適切な文法構造を早期に検出できるようになります。これにより、パーサーの堅牢性が向上します。
    • アクションブロック: 規則に続く { ... } 内のコードは、この文法規則がマッチしたときに実行されるセマンティックアクションです。
      • $$ = nod(ORANGE, $1, $4);: nod 関数は、Goコンパイラの内部でASTノードを作成するために使用される関数です。ここでは、ORANGE というオペレーションコードを持つASTノードを作成し、そのノードに $1exprsym3_list_r、つまり key, value の部分)と $4expr、つまり collection の部分)を子ノードとして関連付けています。$$ はこの規則の左辺(range_stmt)が返すASTノードであり、このノードが for...range 構文のセマンティックな情報をカプセル化します。
      • $$->etype = 0; // := flag: 生成されたASTノードの etype フィールドを 0 に設定しています。コメントから、これは := 演算子による変数宣言が行われたことを示すフラグであると推測されます。for...range ループでは、ループ変数を := で宣言することが一般的であり、このフラグはそのセマンティックな側面をコンパイラに伝えるために使用されます。

3. for_header 規則の変更

for_headerfor ループのヘッダー部分を解析する規則です。この部分が最も大きく変更されており、for ループの異なる形式の解析ロジックが明確に分離されました。

3.1. 最初の規則の変更(for init; test; incr 形式)

 for_header:
-\tosimple_stmt ';' orange_stmt ';' osimple_stmt
+\tosimple_stmt ';' osimple_stmt ';' osimple_stmt
 	{
-\t\tif($3 != N && $3->op == ORANGE) {\n-\t\t\t$$ = dorange($3);\n-\t\t\t$$->ninit = list($$->ninit, $1);\n-\t\t\t$$->nincr = list($$->nincr, $5);\n-\t\t\tbreak;\n-\t\t}\n \t\t// init ; test ; incr
 \t\tif($5 != N && $5->colas != 0)
 \t\t\tyyerror("cannot declare in the for-increment");
 \t\t$$ = nod(OFOR, N, N);
 \t\t$$->ninit = $1;
 \t\t$$->ntest = $3;
 \t\t$$->nincr = $5;
 	}
  • 変更内容:
    • osimple_stmt ';' orange_stmt ';' osimple_stmtosimple_stmt ';' osimple_stmt ';' osimple_stmt に変更されました。つまり、真ん中の orange_stmtosimple_stmt に置き換えられました。
    • それに伴い、アクションブロック内の if($3 != N && $3->op == ORANGE) { ... } という range 構文を特別扱いするロジックが削除されました。
  • 解説:
    • 関心の分離: 以前は、この規則が通常の for init; test; incr 形式と、for ループの test 部分に range 構文が誤って現れた場合の両方を処理しようとしていました。この変更により、この規則は純粋に for init; test; incr 形式のループに特化しました。これにより、パーサーのロジックが簡素化され、各規則の責任が明確になりました。
    • $1 は初期化ステートメント、$3 は条件式、$5 はインクリメントステートメントに対応します。
    • if($5 != N && $5->colas != 0) yyerror("cannot declare in the for-increment");: これは、Go言語の仕様で許可されていない、for ループのインクリメント部分での新しい変数宣言(:= を使用)を検出するためのエラーチェックです。yyerror 関数は、構文エラーが発生したことをパーサーに通知し、適切なエラーメッセージを出力します。
    • $$ = nod(OFOR, N, N);: OFOR(通常の for ループを表すオペレーションコード)を持つASTノードを作成します。
    • $$->ninit = $1; $$->ntest = $3; $$->nincr = $5;: 生成された for ループのASTノードの ninit(初期化)、ntest(条件)、nincr(インクリメント)フィールドに、それぞれ対応する子ノードを設定しています。これにより、通常の for ループの構造がASTに正確に反映されます。

3.2. 2番目の規則の変更(for condition 形式)

-|\torange_stmt
+|\tosimple_stmt
 	{
-\t\t// range
-\t\tif($1 != N && $1->op == ORANGE) {\n-\t\t\t$$ = dorange($1);\n-\t\t\tbreak;\n-\t\t}\n \t\t// normal test
 \t\t$$ = nod(OFOR, N, N);
 \t\t$$->ninit = N;
 \t\t$$->ntest = $1;
 \t\t$$->nincr = N;
 	}
  • 変更内容: orange_stmtosimple_stmt に変更されました。それに伴い、アクションブロック内の if($1 != N && $1->op == ORANGE) { ... } という range 構文を特別扱いするロジックが削除されました。
  • 解説: 以前は、この規則も for condition 形式と for range 形式の両方を処理しようとしていました。この変更により、この規則は純粋に for condition 形式のループに特化しました。
    • $1 は条件式に対応します。
    • $$ = nod(OFOR, N, N);: OFOR ノードを作成します。
    • $$->ninit = N; $$->ntest = $1; $$->nincr = N;: 初期化とインクリメントがないため、それらのフィールドは N(nil)に設定され、ntest フィールドに条件式が設定されます。これにより、条件式のみの for ループのASTが正確に構築されます。

3.3. 新しい range_stmt 規則の追加(for...range 形式)

+|\trange_stmt
+\t{\n+\t\t$$ = dorange($1);\n+\t\taddtotop($$);\n+\t}\
  • 変更内容: range_stmt という新しい規則が for_header に追加されました。
  • 解説:
    • for...range の独立した処理: この新しい規則は、for...range ループのヘッダーを明示的に処理するために導入されました。これにより、for...range 構文の解析が他の for ループの形式から完全に分離され、文法がよりモジュール化されました。これは、Go言語の for...range が持つ独自のセマンティクスを適切に処理するために不可欠です。
    • $1 は、先に定義された range_stmt 規則から返されたASTノード(for...range の右辺に対応)です。
    • $$ = dorange($1);: dorange 関数は、range 構文のASTノードを受け取り、それをGoコンパイラの内部表現に適した for ループのAST構造に変換する役割を担います。for...range ループは、内部的には通常の for ループに変換される(例えば、イテレータの初期化、条件チェック、次の要素への移動など)ため、この関数はその変換処理を行います。この変換は、コンパイラのバックエンドが for...range を効率的に処理できるようにするために重要です。
    • addtotop($$);: 生成された for...range ループのASTノードを、現在のスコープのトップレベルに追加します。これは、for...range ループが新しいスコープを導入し、その中でループ変数(key, value)が宣言されるため、それらの変数を適切にスコープ管理するために必要です。スコープ管理は、変数名の衝突を防ぎ、プログラムの正確性を保証するために不可欠なコンパイラの機能です。

これらの変更全体として、Goコンパイラの for ループ解析のロジックが大幅に整理され、各 for ループの形式がより明確かつ独立して処理されるようになりました。これにより、コンパイラのコードベースの可読性、保守性、そして将来の拡張性が向上しています。特に、go.y のようなパーサー生成ツールを使用する環境では、文法規則の明確さと簡潔さが、生成されるパーサーの品質と効率に直結します。

関連リンク

  • Go言語仕様: https://go.dev/ref/spec (特に "For statements" のセクションは、for ループの文法とセマンティクスを詳細に記述しています。)
  • Yacc/Bisonのドキュメント:
  • Goコンパイラのソースコード (現在のバージョン): https://github.com/golang/go/tree/master/src/cmd/compile/internal/syntax (現在のGoコンパイラは go.y を直接使用していませんが、概念は共通しています。Go 1.7以降、Goコンパイラは手書きのパーサーに移行しており、scanner.goparser.go などのファイルで構文解析ロジックが実装されています。)

参考にした情報源リンク

  • Go言語の for ループに関する公式ドキュメントやチュートリアル: Goの公式ウェブサイトやGo by Exampleなどのリソースは、for ループの様々な形式とその使用法について詳しく説明しています。
  • Yacc/Bisonの文法定義とアクションに関する一般的な情報: コンパイラ設計の教科書やオンラインのチュートリアルは、Yacc/Bisonの基本的な概念と、文法規則にセマンティックアクションを関連付ける方法について理解を深めるのに役立ちます。
  • Go言語の初期のコミット履歴と開発に関する議論: Go言語の初期の開発段階におけるコミットメッセージやメーリングリストの議論は、言語設計の進化と、特定の変更がなぜ行われたのかについての貴重な洞察を提供します。
  • Go言語のコンパイラ設計に関する一般的な知識: コンパイラ理論に関する一般的な知識は、go.y のような文法ファイルがコンパイルプロセス全体の中でどのように位置づけられるかを理解する上で不可欠です。特に、字句解析、構文解析、意味解析の各フェーズの役割を理解することが重要です。