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

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

このコミットは、Go言語の初期のコミットの一つであり、usr/gri/pretty/scanner.go ファイルにおけるコメント解析のバグ修正に関するものです。具体的には、// スタイルのコメントを解析する際の不具合が修正されています。

コミット

commit 6ddc48b84aa15d70643ebb500db59cd7f78c0adf
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Feb 23 17:16:55 2009 -0800

    - fixed a bug with //-comment parsing
    
    R=r
    OCL=25343
    CL=25343

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

https://github.com/golang/go/commit/6ddc48b84aa15d70643ebb500db59cd7f78c0adf

元コミット内容

- fixed a bug with //-comment parsing

変更の背景

このコミットは、Go言語のコンパイラまたはツールチェインの一部であるスキャナー(字句解析器)において、// で始まるコメントの解析にバグが存在したために行われました。スキャナーはソースコードをトークン(単語)に分解する役割を担っており、コメントも適切にスキップまたは処理する必要があります。このバグにより、// コメントが正しく認識されず、その後の字句解析に悪影響を及ぼす可能性がありました。

Go言語では、// は行コメントを示し、その行の残りの部分はコメントとして扱われます。このコミットは、この基本的な字句解析の挙動を修正し、Go言語のソースコードが正しく解析されるようにするために不可欠でした。

前提知識の解説

スキャナー(字句解析器)

スキャナー(または字句解析器、Lexer)は、コンパイラやインタプリタの最初のフェーズです。その主な役割は、ソースコードの文字列を読み込み、意味のある最小単位である「トークン」のストリームに変換することです。例えば、int x = 10; というコードは、int(キーワード)、x(識別子)、=(演算子)、10(リテラル)、;(区切り文字)といったトークンに分解されます。

スキャナーは、空白文字やコメントなど、プログラムの実行には直接関係しないが構文上必要な要素を適切に処理(通常はスキップ)する必要があります。

コメントの処理

プログラミング言語におけるコメントは、人間がコードを理解しやすくするための注釈であり、コンパイラやインタプリタによって無視されるべき部分です。Go言語には主に2種類のコメントがあります。

  1. 行コメント: // で始まり、行末までがコメントとして扱われます。
  2. ブロックコメント: /* で始まり、*/ で終わるまでの複数行がコメントとして扱われます。

スキャナーは、これらのコメントの開始シーケンス(///*)を検出すると、コメントの終了シーケンス(行末や */)までを読み飛ばし、トークンとしては生成しません。

S.chS.next()

コミットの差分を見ると、S.chS.next() という要素が登場します。これらはスキャナーの実装において一般的なパターンです。

  • S.ch: 現在スキャンしている文字(character)を保持する変数と考えられます。
  • S.next(): 次の文字を読み込み、S.ch を更新するメソッドと考えられます。これは、ソースコードのストリームを1文字ずつ進める役割を担います。

技術的詳細

このコミットは、usr/gri/pretty/scanner.go ファイル内の scanComment() 関数におけるバグを修正しています。

scanComment() 関数は、スキャナーがコメントの開始を検出した際に呼び出され、コメントの内容を読み飛ばす(またはコメントトークンとして処理する)役割を担います。

元のコードでは、// スタイルのコメントを検出した直後(S.ch == '/' の条件が真になった後)に、S.next() が呼び出されていました。

// 変更前
if S.ch == '/' {
    //-style comment
    S.next(); // ここが問題
    for S.ch >= 0 {
        S.next();
        if S.ch == '\n' {
            break;
        }
    }
    // ...
}

この S.next() の呼び出しが問題でした。

  1. スキャナーが // の最初の / を読み込み、S.ch/ になります。
  2. scanComment() が呼び出され、S.ch == '/' の条件が真になります。
  3. 問題の S.next() が実行され、// の2番目の / をスキップしてしまいます。
  4. その後のループ for S.ch >= 0 では、コメントの開始であるはずの2番目の / ではなく、その次の文字からコメントの本体として読み込みを開始してしまいます。

これにより、// コメントの最初の文字が正しく処理されず、コメントの範囲がずれたり、意図しない文字がコメントの一部として扱われたり、あるいはコメントの開始が正しく認識されないといったバグが発生していたと考えられます。

修正は、この余分な S.next() の呼び出しを削除することです。

// 変更後
if S.ch == '/' {
    //-style comment
    // S.next(); // この行が削除された
    for S.ch >= 0 {
        S.next();
        if S.ch == '\n' {
            break;
        }
    }
    // ...
}

この修正により、// の2番目の / がスキップされることなく、正しくコメントの開始として認識され、その後のループでコメントの本体が適切に読み飛ばされるようになります。これにより、// コメントの字句解析が正確に行われるようになりました。

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

変更は usr/gri/pretty/scanner.go ファイルの399行目付近にあります。

--- a/usr/gri/pretty/scanner.go
+++ b/usr/gri/pretty/scanner.go
@@ -399,7 +399,6 @@ func (S *Scanner) scanComment() string {
 
 	if S.ch == '/' {
 		//-style comment
-		S.next();
 		for S.ch >= 0 {
 			S.next();
 			if S.ch == '\n' {

具体的には、S.next(); の1行が削除されています。

コアとなるコードの解説

scanComment() 関数は、スキャナーがコメントを処理するための内部関数です。この関数は、現在の文字 S.ch がコメントの開始文字(この場合は /)であることを前提として呼び出されます。

変更前のコードでは、if S.ch == '/' のブロック内で、// コメントを処理するロジックが開始されていました。このブロックに入った時点で、S.ch は最初の / を指しています。

問題の行 S.next(); は、この最初の / を処理した後、すぐに次の文字(つまり // の2番目の /)を読み飛ばしていました。これは、コメントの開始シーケンス // を正しく認識するために、2番目の / も含めて処理する必要があるにもかかわらず、それを無視してしまっていたことを意味します。

この S.next(); を削除することで、if S.ch == '/' のブロックに入った後、最初の / はそのままに、次の for ループが実行されます。この for ループの最初の S.next(); が、// の2番目の / を読み込み、その後コメントの残りの部分を正しく読み飛ばすようになります。これにより、// コメントの字句解析が期待通りに機能するようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の字句解析に関する一般的な情報(Go言語の公式ドキュメントやコンパイラの設計に関する資料など)
  • コンパイラの設計に関する一般的な書籍やオンラインリソース(字句解析の概念について)
  • Go言語のソースコード(特にgo/scannerパッケージや関連する字句解析器の実装)