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

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

このコミットは、Go言語のcmd/yaccツールにおける挙動の修正に関するものです。具体的には、yaccツールが生成するGoコードにおいて、-l--nolines)フラグが指定された場合に、ソースコードの行番号を示すコメント(//lineコメント)が出力されないように変更されています。これにより、生成されるコードの冗長性が減り、デバッグや可読性の向上に寄与します。

コミット

commit 2eeab323ad516dca2fb6f222a1810b3fadd61fc1
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed May 15 12:08:51 2013 +0800

    cmd/yacc: don't emit line comment when -l is given
    Fixes #5447.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/9343045

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

https://github.com/golang/go/commit/2eeab323ad516dca2fb6f222a1810b3fadd61fc1

元コミット内容

cmd/yacc: don't emit line comment when -l is given Fixes #5447.

このコミットは、cmd/yaccツールが-lオプション(--nolines)を受け取った際に、生成されるGoコード内に//lineコメントを出力しないようにするものです。これは、Issue #5447で報告された問題の修正に対応しています。

変更の背景

yaccのようなパーサジェネレータは、文法定義ファイルからソースコードを生成します。通常、生成されたコードには、元の文法定義ファイルのどの行からコードが生成されたかを示す//lineコメントが含まれます。これは、コンパイルエラーやランタイムエラーが発生した際に、生成されたコードではなく元の文法定義ファイルの行番号を参照できるようにするため、デバッグに非常に役立ちます。

しかし、場合によっては、これらの//lineコメントが不要、あるいは邪魔になることがあります。例えば、生成されたコードのサイズを最小限に抑えたい場合や、生成されたコード自体を直接デバッグするのではなく、元の文法定義ファイルのみを参照してデバッグを行うワークフローの場合などです。

Go言語のcmd/yaccツールには、このような//lineコメントの出力を抑制するための-l--nolines)フラグが存在していました。しかし、このコミットが修正する問題(Issue #5447)は、この-lフラグが正しく機能せず、フラグが指定されても//lineコメントが出力されてしまうというバグでした。このバグにより、ユーザーは生成されるコードから不要なコメントを削除するために手動での後処理が必要になるなど、不便を強いられていました。

このコミットは、このバグを修正し、-lフラグの意図された挙動を回復させることで、ユーザーが生成コードのコメント出力をより細かく制御できるようにすることを目的としています。

前提知識の解説

Yacc (Yet Another Compiler Compiler)

Yaccは「Yet Another Compiler Compiler」の略で、コンパイラのパーサ部分を自動生成するためのツールです。BNF(Backus-Naur Form)やEBNF(Extended Backus-Naur Form)のような形式で記述された文法定義を読み込み、その文法を解析するためのC言語(または他の言語)のソースコードを生成します。生成されるコードは、通常、LALR(1)パーサと呼ばれる種類のパーサです。

Yaccは、字句解析器(Lexer)を生成するLex(またはFlex)と組み合わせて使用されることが多く、Lexが入力ストリームをトークンに分割し、Yaccがそのトークン列を文法規則に従って解析します。

cmd/yacc

cmd/yaccは、Go言語で実装されたYacc互換のパーサジェネレータです。Go言語のプロジェクトでパーサを生成する際に使用されます。基本的な機能はオリジナルのYaccと同様ですが、Go言語のコードを生成するという点で異なります。

//lineコメント

Go言語のソースコードにおいて、//lineコメントは、コンパイラに対して、その行以降のコードが別のファイルや別の行番号から来ているかのように扱うよう指示する特殊なコメントです。

構文は以下の通りです。 //line <ファイル名>:<行番号>

例えば、//line myparser.y:100というコメントがあれば、その後のコードはmyparser.yファイルの100行目から来ているとコンパイラに認識させることができます。これは、特にコードジェネレータによって生成されたファイルで非常に有用です。生成されたコードでエラーが発生した場合でも、元のソースファイル(この場合はmyparser.y)の対応する行番号をエラーメッセージに表示できるため、デバッグが容易になります。

-l (または --nolines) フラグ

Yaccやその派生ツールにおいて、-lまたは--nolinesフラグは、生成されるソースコードに//lineコメントを含めないようにするためのオプションです。このフラグを使用することで、生成されるファイルのサイズを削減したり、生成されたコードの可読性を向上させたりすることができます。

技術的詳細

このコミットは、src/cmd/yacc/yacc.goファイル内の3つの箇所で、//lineコメントの出力ロジックに条件分岐を追加しています。変更の核心は、グローバル変数lflag-lオプションが指定された場合にtrueとなるフラグ)の値をチェックし、lflagtrueの場合には//lineコメントの出力をスキップするという点です。

具体的には、以下の3つの関数内のfmt.Fprintf呼び出しに条件が追加されています。

  1. emitcode関数内: この関数は、ユーザーが提供するアクションコード(文法規則に対応するGoコード)を生成する際に使用されます。元のコードでは、import __yyfmt__ "fmt"のインポート文の後に、常に//line %v:%v形式のコメントが出力されていました。 変更後: if !lflag { fmt.Fprintf(ftable, "//line %v:%v\\n\\t\\t", infile, lineno+i) } これにより、-lフラグが設定されている場合は、この//lineコメントが出力されなくなります。

  2. output関数内: この関数は、パーサのテーブルデータ(状態遷移テーブルなど)を生成する際に使用されます。元のコードでは、fmt.Fprintf(ftable, "\\n//line yacctab:1\\n")という行が常に存在し、yacctabという仮想ファイルからの行コメントを出力していました。 変更後: if !lflag { fmt.Fprintf(ftable, "\\n//line yacctab:1") } これにより、-lフラグが設定されている場合は、この//lineコメントが出力されなくなります。

  3. others関数内: この関数は、yaccpar(Yaccが生成するパーサの共通部分)のコードをコピーする際に使用されます。元のコードでは、fmt.Fprintf(ftable, "\\n//line yaccpar:1\\n")という行が常に存在し、yaccparという仮想ファイルからの行コメントを出力していました。 変更後: if !lflag { fmt.Fprintf(ftable, "\\n//line yaccpar:1\\n") } これにより、-lフラグが設定されている場合は、この//lineコメントが出力されなくなります。

これらの変更により、lflagtrueの場合、つまり-lオプションが指定された場合に、cmd/yaccが生成するGoコードからすべての//lineコメントが抑制されるようになります。

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

--- a/src/cmd/yacc/yacc.go
+++ b/src/cmd/yacc/yacc.go
@@ -1130,7 +1130,9 @@ func emitcode(code []rune, lineno int) {
 		writecode(line)
 		if !fmtImported && isPackageClause(line) {
 			fmt.Fprintln(ftable, `import __yyfmt__ "fmt"`)
-			fmt.Fprintf(ftable, "//line %v:%v\\n\\t\\t", infile, lineno+i)
+			if !lflag {
+				fmt.Fprintf(ftable, "//line %v:%v\\n\\t\\t", infile, lineno+i)
+			}
 			fmtImported = true
 		}
 	}
@@ -2193,8 +2195,10 @@ nextk:
 func output() {
 	var c, u, v int
 
-	fmt.Fprintf(ftable, "\n//line yacctab:1\n")
-	fmt.Fprintf(ftable, "var %sExca = []int{\n", prefix)
+	if !lflag {
+		fmt.Fprintf(ftable, "\n//line yacctab:1")
+	}
+	fmt.Fprintf(ftable, "\nvar %sExca = []int{\n", prefix)
 
 	noset := mkset()
 
@@ -2963,7 +2967,9 @@ func others() {
 	}
 
 	// copy yaccpar
-	fmt.Fprintf(ftable, "\n//line yaccpar:1\n")
+	if !lflag {
+		fmt.Fprintf(ftable, "\n//line yaccpar:1\n")
+	}
 
 	parts := strings.SplitN(yaccpar, prefix+"run()", 2)
 	fmt.Fprintf(ftable, "%v", parts[0])

コアとなるコードの解説

上記の差分は、src/cmd/yacc/yacc.goファイル内の3つの異なる箇所で、//lineコメントの出力に条件分岐を追加していることを示しています。

  1. emitcode関数内の変更: 元のコードでは、import __yyfmt__ "fmt"の後に、常に//lineコメントが出力されていました。これは、生成されるGoコードが、元の文法定義ファイル(infile)の特定の行(lineno+i)から来ていることを示すためのものです。 変更後のif !lflag { ... }という条件は、コマンドライン引数で-lフラグが指定されていない場合(つまりlflagfalseの場合)にのみ、この//lineコメントを出力するようにします。lflagtrue-lが指定された)であれば、このコメントはスキップされます。

  2. output関数内の変更: この関数は、パーサの内部テーブル(%sExca配列など)を生成します。元のコードでは、これらのテーブルの前に//line yacctab:1というコメントが挿入されていました。これは、これらのデータがyacctabという仮想的なファイル(Yaccの内部的なテーブル定義)の1行目から来ていることを示すものです。 ここでもif !lflag { ... }という条件が追加され、-lフラグが指定されている場合は、この//lineコメントが出力されなくなります。

  3. others関数内の変更: この関数は、Yaccパーサの共通ランタイムコードであるyaccparを生成されたファイルにコピーします。元のコードでは、yaccparのコードの前に//line yaccpar:1というコメントが挿入されていました。これは、yaccparのコードがyaccparという仮想的なファイル(Yaccのランタイム定義)の1行目から来ていることを示すものです。 同様にif !lflag { ... }という条件が追加され、-lフラグが指定されている場合は、この//lineコメントが出力されなくなります。

これらの変更により、cmd/yacc-lフラグの意図された挙動を正しく実装し、生成されるGoコードから不要な//lineコメントを抑制できるようになりました。これにより、生成コードのクリーンアップや、特定のビルドプロセスにおける要件を満たすことが可能になります。

関連リンク

参考にした情報源リンク