[インデックス 1760] ファイルの概要
このコミットは、Goコンパイラのパーサー定義ファイルである src/cmd/gc/go.y
と、字句解析に関連する src/cmd/gc/lex.c
に加え、テストファイル test/bugs/bug085.go
および test/bugs/bug129.go
の移動と、test/golden.out
の更新を含んでいます。主な目的は、Go言語の構文解析におけるバグ修正と、パーサーの曖昧性解消です。
コミット
commit 63985b489b2ad5307de221120df39fbeb66532eb
Author: Russ Cox <rsc@golang.org>
Date: Thu Mar 5 15:57:03 2009 -0800
bug085 bug129
R=ken
OCL=25787
CL=25791
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/63985b489b2ad5307de221120df39fbeb66532eb
元コミット内容
bug085 bug129
R=ken
OCL=25787
CL=25791
変更の背景
このコミットは、Goコンパイラにおける2つの特定のバグ、bug085
と bug129
の修正を目的としています。
- bug085: このバグは、Goのパッケージ内での変数スコープと階層に関する「未定義」エラーに関連していました。具体的には、
P
パッケージ内で宣言された変数x
が、同じパッケージ内の関数foo
からアクセスされた際に「未定義」と誤って報告され、コンパイル時または実行時に「Bus error」を引き起こす可能性がありました。この修正は、Goコンパイラがパッケージスコープ内の変数の可視性を正しく解決することを保証します。 - bug129: このバグは、ローカル変数名がインポートされたパッケージ名をシャドウイングする際に発生する問題に関連していました。例えば、
fmt
パッケージをインポートしているにもかかわらず、関数内でfmt
という名前のローカル変数を宣言すると、コンパイラがこれを正しく処理できないケースがありました。この修正は、このような変数シャドウイングのケースが正しく識別され、コンパイルエラーや予期せぬ動作を引き起こさないようにすることを目的としています。
これらのバグは、Goコンパイラのパーサーが特定の構文構造を正しく解釈できないことに起因しており、特にYaccによって生成されるパーサーにおける「reduce/reduce conflict」のような曖昧性の問題が関与していた可能性があります。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
- コンパイラのフロントエンド: コンパイラは通常、ソースコードを機械語に変換する複数のフェーズに分かれています。フロントエンドは、ソースコードを解析し、中間表現(抽象構文木など)を生成する部分です。これには、字句解析(Lexical Analysis)と構文解析(Syntax Analysis)が含まれます。
- 字句解析 (Lexical Analysis): ソースコードを読み込み、意味のある最小単位である「トークン」のストリームに変換するプロセスです。この処理を行うプログラムを「レキサー (Lexer)」または「スキャナー (Scanner)」と呼びます。Goコンパイラでは、
src/cmd/gc/lex.c
がこの役割の一部を担っていた可能性がありますが、現代のGoコンパイラの字句解析は主にGo言語で書かれたsrc/cmd/compile/internal/syntax/lex.go
で行われています。 - 構文解析 (Syntax Analysis): トークンのストリームを文法規則に従って解析し、プログラムの構造を表現する「抽象構文木 (Abstract Syntax Tree, AST)」を構築するプロセスです。この処理を行うプログラムを「パーサー (Parser)」と呼びます。
- Yacc (Yet Another Compiler Compiler): Yaccは、文法定義ファイル(通常
.y
拡張子を持つ)からC言語のパーサーコードを生成するツールです。Goコンパイラの初期段階では、src/cmd/gc/go.y
がGo言語の文法を定義するために使用されていました。YaccはLALR(1)パーサーを生成します。 - reduce/reduce conflict (還元/還元衝突): Yaccのようなパーサー生成器において発生する文法上の曖昧性の一種です。これは、パーサーが入力ストリームの特定の時点で、スタック上のシンボルのシーケンスが複数の異なる文法規則の右辺に一致し、どの規則を使って「還元 (reduce)」すればよいかを決定できない場合に発生します。Yaccはデフォルトで、文法ファイル内で先に定義されている規則を優先して衝突を解決しますが、これは必ずしも意図した動作とは限りません。
%prec
ディレクティブ: Yaccにおいて、演算子の優先順位や結合規則を明示的に指定するために使用されるディレクティブです。特に、シフト/還元衝突 (shift/reduce conflict) や還元/還元衝突を解決するために、特定の規則に優先順位を割り当てることができます。これにより、文法が曖昧な場合でも、パーサーの動作を制御できます。
技術的詳細
このコミットの技術的詳細の核心は、Goコンパイラのパーサーにおける文法規則の調整と、それに伴う字句解析器の挙動の微調整にあります。
src/cmd/gc/go.y
はGo言語の文法をYacc形式で記述したファイルです。このファイルへの変更は、主に以下の問題に対処しています。
- 還元/還元衝突の解消: コミットメッセージの差分に明示的に「this rule introduces 1 reduce/reduce conflict with the rule lpack: LPACK above. the reduce/reduce conflict is only with lookahead '.', in which case the correct resolution is the lpack rule. (and it wins because it is above.)」と記述されているように、
name
規則とlpack
規則の間で還元/還元衝突が発生していました。これは、LPACK
(パッケージ名を表すトークン)が、通常の識別子(name
)としても解釈されうる状況で、パーサーがどちらの規則を適用すべきか判断に迷うために起こります。- この衝突を解決するために、
%left
ディレクティブが導入され、NotPackage
とLPACKAGE
、NotDot
と.
、NotParen
と(
の間に優先順位が設定されました。これにより、特定のトークン(例:LPACKAGE
や.
、(
)が出現した場合に、パーサーがどの規則を優先して適用すべきかが明確になります。 - 特に、
name
規則にLPACK %prec NotDot
が追加されたことで、LPACK
が.
の前に来た場合にname
として還元されることを防ぎ、lpack
規則が優先されるように制御しています。
- この衝突を解決するために、
- ラベル名の導入:
labelname
という新しい型が導入され、name
またはkeyword
がラベルとして使用できることを示しています。これにより、goto
文などで使用されるラベルの構文がより柔軟になります。 - 文字列リテラルの型推論:
pexpr
規則において、文字列リテラル (CTSTR
) の場合にその型をtypes[TSTRING]
(文字列型) に明示的に設定する処理が追加されました。これにより、文字列リテラルの型が正しく推論されるようになります。 package
宣言の制約強化:package
規則に%prec NotPackage
が追加され、「package statement must be first」というエラーメッセージが出力されるように、package
宣言がファイルの先頭になければならないという制約が強化されました。laconst
規則の変更:laconst
規則からLPACK
のケースが削除されました。これは、LPACK
がパッケージ名として扱われるべきであり、定数としては不適切であるという文法的な修正です。
src/cmd/gc/lex.c
への変更は、mkpackage
関数内で lookup(package)->lexical = LPACK;
という行が追加されたことです。これは、パッケージ名を字句解析器のシンボルテーブルに LPACK
として登録することで、その名前がパッケージとして認識されるようにするための変更です。これにより、bug129
のような、ローカル変数がパッケージ名をシャドウイングする問題の解決に寄与します。
test/bugs/bug085.go
と test/bugs/bug129.go
が test/fixedbugs/
ディレクトリに移動されたのは、これらのバグが修正されたことを示しています。test/golden.out
の変更は、これらのバグ修正によってコンパイラの出力(特にエラーメッセージ)が変化したことを反映しています。
コアとなるコードの変更箇所
src/cmd/gc/go.y
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -55,7 +55,7 @@
%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
+%type <node> name labelname onew_name new_name new_name_list_r new_field
%type <node> vardcl_list_r vardcl Avardcl Bvardcl
%type <node> interfacedcl_list_r interfacedcl interfacedcl1
%type <node> structdcl_list_r structdcl embed
@@ -95,6 +95,23 @@
%left '{'
%left Condition
+/*
+ * resolve LPACKAGE vs not in favor of LPACKAGE
+ */
+%left NotPackage
+%left LPACKAGE
+
+/*
+ * resolve '.' vs not in favor of '.'
+ */
+%left NotDot
+%left '.'
+
+/*
+ * resolve '(' vs not in favor of '('
+ */
+%left NotParen
+%left '('
%%
file:
@@ -107,6 +124,7 @@ file:
}
package:
+\t%prec NotPackage
{\
\tyyerror("package statement must be first");
\tmkpackage("main");
@@ -776,6 +794,8 @@ pexpr:
{\
\t$$ = nod(OLITERAL, N, N);
\t$$->val = $1;
+\t\tif($1.ctype == CTSTR)
+\t\t\t$$->type = types[TSTRING];
}
|\tlaconst
{\
@@ -999,6 +1019,25 @@ name:
{\
\t$$ = oldname($1);
}
+\t/*
+\t * this rule introduces 1 reduce/reduce conflict
+\t * with the rule lpack: LPACK above.
+\t * the reduce/reduce conflict is only with
+\t * lookahead '.', in which case the correct
+\t * resolution is the lpack rule. (and it wins
+\t * because it is above.)
+\t */
+|\tLPACK %prec NotDot
+\t{\
+\t\t$$ = oldname($1);
+\t}
+
+labelname:
+\tname
+|\tkeyword
+\t{\
+\t\t$$ = oldname($1);
+\t}
convtype:
latype
@@ -1311,6 +1350,7 @@ Afnres:
}
Bfnres:
+\t%prec NotParen
{\
\t$$ = N;
}
@@ -1515,7 +1555,7 @@ Astmt:
{\
\t$$ = N;
}
-|\tnew_name ':'
+|\tlabelname ':'
{\
\t$$ = nod(OLABEL, $1, N);
}
@@ -1961,15 +2001,7 @@ lpack:
}
laconst:
-\tLPACK
-\t{\
-\t\t// for LALR(1) reasons, using laconst works here
-\t\t// but lname does not. even so, the messages make
-\t\t// more sense saying "var" instead of "const".
-\t\tyyerror("%s is package, not var", $1->name);
-\t\tYYERROR;
-\t}
-|\tLATYPE
+\tLATYPE
{\
\tyyerror("%s is type, not var", $1->name);
\tYYERROR;
src/cmd/gc/lex.c
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1341,6 +1341,9 @@ mkpackage(char* pkg)\
\ts->opackage = package;\
}\
+\t// declare this name as a package
+\tlookup(package)->lexical = LPACK;\
+\
\tif(outfile == nil) {\
\t\t// BOTCH need to get .6 from backend\
\t\tp = strrchr(infile, '/');
test/bugs/bug085.go
および test/bugs/bug129.go
これらのファイルは test/bugs/
から test/fixedbugs/
へ移動されました。これは、これらのバグが修正され、テストが「修正済みバグ」のカテゴリに属することを示す標準的なプラクティスです。
コアとなるコードの解説
src/cmd/gc/go.y
の変更点
%type <node> name labelname ...
:labelname
という新しい型がname
と並んで追加されました。これは、Go言語のラベル(goto
文などで使用)が、通常の識別子だけでなく、キーワードも使用できるようになったことを示唆しています。
%left
ディレクティブの追加:NotPackage
,LPACKAGE
,NotDot
,.
,NotParen
,(
の間に優先順位が設定されました。これは、Yaccパーサーにおける還元/還元衝突やシフト/還元衝突を解決するための典型的な手法です。LPACKAGE
がNotPackage
よりも高い優先順位を持つことで、package
キーワードが他の識別子と衝突する可能性のある文脈で、package
として正しく解釈されるようにします。.
がNotDot
よりも高い優先順位を持つことで、ドット演算子(.
)が他の文脈で識別子と衝突する可能性のある文脈で、ドット演算子として正しく解釈されるようにします。(
がNotParen
よりも高い優先順位を持つことで、括弧が他の文脈で識別子と衝突する可能性のある文脈で、括弧として正しく解釈されるようにします。
package:
規則への%prec NotPackage
の追加:- これにより、
package
宣言がファイルの先頭にない場合にyyerror("package statement must be first")
が発生するよう、その優先順位が明示的に設定されました。これは、package
宣言の配置に関する文法的な制約を強化するものです。
- これにより、
pexpr:
規則における文字列リテラルの型設定:if($1.ctype == CTSTR) $$->type = types[TSTRING];
の追加により、文字列リテラルが構文解析された際に、そのノードの型が明示的に文字列型 (TSTRING
) として設定されるようになりました。これにより、コンパイラが文字列リテラルを正しく型付けし、後続の型チェックフェーズで利用できるようになります。
name:
規則へのLPACK %prec NotDot
の追加:- この変更は、
name
規則とlpack
規則の間で発生していた還元/還元衝突を解決するためのものです。コメントにもあるように、ルックアヘッドが.
の場合に衝突が発生していましたが、%prec NotDot
を使用することで、LPACK
が.
の前に来た場合にname
として還元されることを防ぎ、lpack
規則が優先されるようにしています。これにより、package
名が識別子として誤って解釈されることを防ぎます。
- この変更は、
labelname:
規則の追加:labelname
はname
またはkeyword
のいずれかとして定義されました。これは、Go言語のラベルが、通常の識別子だけでなく、予約語(キーワード)も使用できるようになったことを意味します。これにより、goto
文などで使用されるラベルの柔軟性が向上します。
Bfnres:
規則への%prec NotParen
の追加:- この変更は、関数結果の構文解析における曖昧性を解消するためのものです。
%prec NotParen
を使用することで、括弧の有無によって異なる解釈がされる可能性のある文脈で、パーサーの動作を明確にしています。
- この変更は、関数結果の構文解析における曖昧性を解消するためのものです。
Astmt:
規則におけるnew_name
からlabelname
への変更:new_name ':'
がlabelname ':'
に変更されました。これは、ラベルの定義において、new_name
(新しい識別子)だけでなく、labelname
(識別子またはキーワード)が使用できるようになったことを反映しています。これにより、labelname
規則の導入と一貫性が保たれます。
laconst:
規則からのLPACK
の削除:laconst
(定数)規則からLPACK
(パッケージ名)のケースが削除されました。これは、パッケージ名が定数として扱われるべきではないという文法的な修正です。これにより、"syscall is package, not var"
のようなエラーが適切に報告されるようになります。
src/cmd/gc/lex.c
の変更点
mkpackage
関数へのlookup(package)->lexical = LPACK;
の追加:mkpackage
関数は、パッケージが宣言されたときに呼び出されます。この変更により、宣言されたパッケージ名が字句解析器のシンボルテーブル内でLPACK
(パッケージを表すトークン)として明示的にマークされるようになりました。これにより、字句解析器がその名前を通常の識別子ではなく、パッケージ名として認識するようになります。これはbug129
のような、ローカル変数がパッケージ名をシャドウイングする問題の解決に直接寄与します。例えば、fmt
というパッケージがインポートされている場合、fmt
という名前がLPACK
として登録されることで、コンパイラはfmt
をパッケージとして優先的に扱い、ローカル変数との衝突を適切に処理できるようになります。
これらの変更は、Goコンパイラの初期段階における文法解析の堅牢性を高め、特定の構文上の曖昧性やバグを解消するために行われました。特に、Yaccの %prec
ディレクティブを効果的に使用して、パーサーの動作を制御している点が注目されます。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Yacc (Wikipedia): https://ja.wikipedia.org/wiki/Yacc
- Goコンパイラのソースコード (現在の
syntax
パッケージ): https://pkg.go.dev/cmd/compile/internal/syntax
参考にした情報源リンク
- Go bug085 の詳細: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGKdeE8FsGOqVdEbGyvVTb_ikiZ3fLTb0zWWWu_qMXwJ-ObYzMdeD16mzranD9X2PJuEs8MQiKIW2Ozkptl3xkGPjmSEK0u1EqFhy-fsiK5YLU_GPKZuCvTmucevVtTevPOs7FL01dySyzVEHuPTRSApJLi1PtaIPXHeM6a2uR7DRUOlVa2CftLRBW1EubuKtY=
- Go bug129 の詳細: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEKamdThaAGWllvSk298rJZ6BKH-9g8-iYJN_LY3MgaKh8Dk5VJdX_BmL2RV1lLelZ-xQXnFttMMyKpSV5Iq4Q-67sGjL8E34xWKikJaNlBlXbVg-tDBDRc2tUMJiFHs2NrF7UClhBAXDfHjJgeblW3wRchlNCllUsbpPFMiQXLmbX-_MgZDGA2q9A2xyBOnr8P_jSu1ZX0HOAXm-12JJoYNrSKSYgiolJtOqPfqwAasS4F2K-xRw==
- Go compiler go.y の役割: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH4IUlCa1rRe19F6JvqEUG3yqgCXdulSe9U6d3PMJYVmaj7WVnJwwVzT8BB6nayMgFw4oBoJ2fB9NBqhFTtH4aRvvljlCZBl1O5T3qqQDzzdLlfsN5HYwcQQOIRPFtEUZ24xPHh8NW90NYk
- Go compiler lex.c の役割 (現代のGoコンパイラにおける字句解析): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE2NcjP_mBEJxBsFfXAIBg9N8J2Ov9EpFROek6xXVoCzNPf7xSTCvluQpVwuySZlUKlMj9WszODQCDvULbN0x-eWkA6WxcoayWlGl9PJ_2ywhZOWKFJN7agNRHfaO_TpotALlN7pTjZhwlVzTeNjz2hSIrCeNQBCA1IlgwBxWP7f9YlSbhYfG3JOA==
- Yacc grammar reduce/reduce conflict の解説: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEzmRbv38WCr3JL4VdPCdFdnpG2IjbtBwfvV2XDdQf_VRwtcChxG4PPPUQBRdRHbU4OZgrDPVAH9Yx7rRk1AiJGQto1AMcbY4y2yppXuLJs2F_G65HvA5yHX3QCHEeOUFNECQNhUGwQWohcjBCn22xoIHZLGLvSy7lA1dpmZZImyek=