[インデックス 17592] ファイルの概要
このコミットは、Go言語のyacc
ツール、具体的にはsrc/cmd/yacc/yacc.go
ファイルに対する変更です。yacc.go
は、Go言語で書かれたyacc
(Yet Another Compiler Compiler)の実装であり、字句解析器(lexer)と構文解析器(parser)の生成に関連するコードを含んでいます。このファイルは、Go言語のコンパイラツールチェーンの一部として、文法定義からGoのソースコードを生成するために使用されます。
コミット
commit 27cb23ceb191074216f462b521dd67380eebed75
Author: Jamie Wilkinson <jaq@spacepants.org>
Date: Fri Sep 13 13:18:02 2013 +1000
goyacc: Fix debug printing of the lexed token's ID and name, and add whitespace in the 'stateX saw' message.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/13352048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/27cb23ceb191074216f462b521dd67380eebed75
元コミット内容
goyacc: Fix debug printing of the lexed token's ID and name, and add whitespace in the 'stateX saw' message.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/13352048
変更の背景
このコミットの背景には、goyacc
ツールが生成するデバッグ出力の可読性向上が挙げられます。コンパイラやパーサーの開発において、字句解析(lexing)や構文解析(parsing)の過程をデバッグすることは非常に重要です。特に、どのトークンがどのように認識され、パーサーがどの状態に遷移したかといった情報は、問題の特定に不可欠です。
元のデバッグ出力は、トークンのIDと名前の表示順序が直感的でなかったり、状態遷移メッセージのフォーマットにスペースが不足していたりしたため、開発者がデバッグ情報を素早く理解する上で不便がありました。このコミットは、これらのデバッグ出力のフォーマットを改善し、より人間が読みやすい形にすることで、goyacc
を利用した開発者のデバッグ体験を向上させることを目的としています。
具体的には、以下の2点が改善されました。
- 字句解析されたトークンのデバッグ出力において、トークン名とID(数値)の表示順序を入れ替え、IDを括弧で囲むことで、より一般的な「名前(ID)」という形式に統一しました。
- パーサーの状態遷移を示すデバッグ出力において、「stateXsawY」のように連結されていたメッセージにスペースを追加し、「stateX saw Y」のようにすることで、単語の区切りを明確にしました。
これらの変更は、機能的な修正ではなく、主に開発者向けの診断情報の品質向上を目的としたものです。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
-
コンパイラとパーサー:
- コンパイラ: ソースコードを機械が実行できる形式(機械語など)に変換するプログラムです。
- パーサー(構文解析器): コンパイラの主要な段階の一つで、字句解析器から受け取ったトークンの並びが、言語の文法規則に合致しているかを検証し、通常は抽象構文木(AST)を構築します。
- 字句解析器(lexer/scanner): ソースコードを読み込み、意味のある最小単位である「トークン」(キーワード、識別子、演算子など)に分割します。
-
Yacc (Yet Another Compiler Compiler):
- Yaccは、BNF(Backus-Naur Form)などの文法定義から、C言語などのプログラミング言語で書かれた構文解析器のソースコードを自動生成するツールです。これにより、手動でパーサーを書く手間を省き、文法変更への対応を容易にします。
goyacc
: Go言語版のYaccです。Go言語の文法定義からGo言語のパーサーコードを生成するために使用されます。
-
トークン:
- 字句解析器によって識別される、プログラムの最小単位です。例えば、
if
はキーワードトークン、myVariable
は識別子トークン、+
は演算子トークンなどです。 - 各トークンには、その種類を識別するための「ID」(通常は整数値)と、人間が理解しやすい「名前」(文字列)が割り当てられます。
- 字句解析器によって識別される、プログラムの最小単位です。例えば、
-
デバッグ出力:
- プログラムの実行中に、内部状態や処理の流れに関する情報を表示する機能です。開発者がプログラムの動作を理解し、問題を特定するために利用されます。
Printf
のようなフォーマット済み出力関数がよく使われます。
-
$$Debug
変数:yacc
やgoyacc
が生成するパーサーコードには、デバッグレベルを制御するための内部変数(例:$$Debug
)がしばしば含まれます。この変数の値に応じて、出力されるデバッグ情報の詳細度が変わります。値が大きいほど、より詳細な情報が出力されます。
技術的詳細
このコミットは、src/cmd/yacc/yacc.go
ファイル内の2つのデバッグ出力フォーマットを変更しています。
-
字句解析トークンのデバッグ出力フォーマットの変更:
- 変更前:
__yyfmt__.Printf("lex %U %s\\n", uint(char), $$Tokname(c))
- このフォーマットでは、
%U
がUnicodeコードポイント(トークンの数値ID)を表示し、%s
がトークン名を表示していました。出力は「lex <数値ID> <トークン名>
」の形式でした。
- このフォーマットでは、
- 変更後:
__yyfmt__.Printf("lex %s(%d)\\n", $$Tokname(c), uint(char))
- このフォーマットでは、
%s
がトークン名を表示し、%d
がトークンの数値IDを括弧で囲んで表示します。出力は「lex <トークン名>(<数値ID>)
」の形式になります。 - この変更により、トークン名が先に表示され、その後に数値IDが括弧で囲まれるため、人間にとってより直感的で読みやすい形式になりました。例えば、「
lex 65 'A'
」が「lex 'A'(65)
」のようになります。
- このフォーマットでは、
- 変更前:
-
パーサー状態遷移メッセージのデバッグ出力フォーマットの変更:
- 変更前:
__yyfmt__.Printf("saw %s\\n", $$Tokname($$char))
- この行は、パーサーが特定のトークンを「見た(saw)」ことを示すメッセージを出力していました。この行の前に、
__yyfmt__.Printf("%s", $$Statname($$state))
という行があり、現在のパーサーの状態名を出力していました。 - この2つの
Printf
が連続して実行されると、間にスペースがないため、例えば「stateXsawY
」のように連結されて表示されていました。
- この行は、パーサーが特定のトークンを「見た(saw)」ことを示すメッセージを出力していました。この行の前に、
- 変更後:
__yyfmt__.Printf(" saw %s\\n", $$Tokname($$char))
- 変更後の行では、
" saw %s\\n"
のように、saw
の前にスペースが追加されました。 - これにより、前の
Printf
が出力する状態名と、このPrintf
が出力する「saw」メッセージの間にスペースが挿入され、出力が「stateX saw Y
」のようになり、単語の区切りが明確になり可読性が向上します。
- 変更後の行では、
- 変更前:
これらの変更は、Go言語のyacc
ツールが生成するデバッグ出力のフォーマットを微調整し、開発者がパーサーの動作を追跡しやすくすることを目的としています。これは、コンパイラ開発におけるデバッグの効率性を高めるための典型的な改善です。
コアとなるコードの変更箇所
--- a/src/cmd/yacc/yacc.go
+++ b/src/cmd/yacc/yacc.go
@@ -3281,7 +3281,7 @@ out:
\tc = $$Tok2[1] /* unknown char */
}\
if $$Debug >= 3 {\
-\t\t__yyfmt__.Printf(\"lex %U %s\\n\", uint(char), $$Tokname(c))\
+\t\t__yyfmt__.Printf(\"lex %s(%d)\\n\", $$Tokname(c), uint(char))\
}\
return c
}\
@@ -3378,7 +3378,7 @@ $$default:\
\t\tNerrs++
\t\tif $$Debug >= 1 {\
\t\t\t\t__yyfmt__.Printf(\"%s\", $$Statname($$state))\
-\t\t\t\t__yyfmt__.Printf(\"saw %s\\n\", $$Tokname($$char))\
+\t\t\t\t__yyfmt__.Printf(\" saw %s\\n\", $$Tokname($$char))\
\t\t}\
\t\tfallthrough
\
コアとなるコードの解説
上記の差分は、src/cmd/yacc/yacc.go
ファイル内の2つの異なる箇所におけるデバッグ出力の変更を示しています。
-
1つ目の変更 (
@@ -3281,7 +3281,7 @@
):- このコードブロックは、字句解析器(lexer)がトークンを読み込む部分に関連しています。
$$Debug >= 3
という条件は、デバッグレベルが3以上の場合にこのデバッグメッセージが出力されることを意味します。 - 変更前:
__yyfmt__.Printf("lex %U %s\\n", uint(char), $$Tokname(c))
uint(char)
は、字句解析された文字(トークンID)のUnicodeコードポイントを表します。$$Tokname(c)
は、そのトークンIDに対応するトークン名(文字列)を返します。- 出力例:
lex 65 'A'
(文字 'A' のUnicode値が65の場合)
- 変更後:
__yyfmt__.Printf("lex %s(%d)\\n", $$Tokname(c), uint(char))
$$Tokname(c)
が最初に表示され、その後にuint(char)
が括弧で囲まれて表示されます。- 出力例:
lex 'A'(65)
- この変更により、デバッグ出力が「トークン名(ID)」という、より一般的な形式になり、どのトークンが認識されたかを一目で理解しやすくなりました。
- このコードブロックは、字句解析器(lexer)がトークンを読み込む部分に関連しています。
-
2つ目の変更 (
@@ -3378,7 +3378,7 @@
):- このコードブロックは、パーサーがエラー処理を行う
$$default
セクションの一部であり、$$Debug >= 1
という条件でデバッグメッセージが出力されます。 - この
Printf
の直前には、現在のパーサーの状態名を出力する__yyfmt__.Printf("%s", $$Statname($$state))
という行があります(差分には含まれていませんが、文脈から推測できます)。 - 変更前:
__yyfmt__.Printf("saw %s\\n", $$Tokname($$char))
- この行は、パーサーが次に処理しようとしている文字(トークン)を「見た(saw)」ことを示します。
- 前の状態名出力と連結されると、例えば「
state12sawIDENTIFIER
」のようになり、読みにくい場合があります。
- 変更後:
__yyfmt__.Printf(" saw %s\\n", $$Tokname($$char))
" saw %s\\n"
のように、saw
の前にスペースが追加されました。- これにより、出力が「
state12 saw IDENTIFIER
」のようになり、状態名と「saw」メッセージの間に明確な区切りができます。
- この変更は、デバッグログの視覚的な区切りを改善し、複数の情報が連続して出力される際の可読性を高めることを目的としています。
- このコードブロックは、パーサーがエラー処理を行う
これらの変更は、goyacc
が生成するパーサーのデバッグ出力を、開発者にとってより有益で理解しやすいものにするための細かな調整です。
関連リンク
- Go CL (Code Review) 13352048: https://golang.org/cl/13352048
参考にした情報源リンク
- Go言語の公式ドキュメント (goyaccに関する詳細情報): https://pkg.go.dev/cmd/yacc
- Yacc (Wikipedia): https://ja.wikipedia.org/wiki/Yacc
- 字句解析 (Wikipedia): https://ja.wikipedia.org/wiki/%E5%AD%97%E5%8F%A5%E8%A7%A3%E6%9E%90
- 構文解析 (Wikipedia): https://ja.wikipedia.org/wiki/%E6%A7%8B%E6%96%87%E8%A7%A3%E6%9E%90