[インデックス 133] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)の字句解析器(lexer)におけるエスケープ文字処理の改善に関するものです。具体的には、escchar
関数のシグネチャが変更され、それに伴いlex.c
内の文字列リテラルおよび文字リテラルの解析ロジックが修正されています。
コミット
このコミットは、Go言語の初期開発段階における字句解析器の内部的な改善を示しています。特に、エスケープ文字の処理方法がより効率的かつ直接的な形に変更されたことが伺えます。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9abf9e8a37dc843ed7ffab7f803bc23f1bd0bf73
元コミット内容
commit 9abf9e8a37dc843ed7ffab7f803bc23f1bd0bf73
Author: Ken Thompson <ken@golang.org>
Date: Sun Jun 8 18:26:23 2008 -0700
nihon
SVN=121620
コミットメッセージは非常に簡潔で、「nihon」という単語とSVNリビジョン番号のみが記載されています。これはGo言語の初期開発における慣習的なもので、詳細な説明はコードの変更自体に委ねられていることが多いです。
変更の背景
この変更の背景には、Go言語のコンパイラが文字列リテラルや文字リテラルを解析する際の内部的な処理の最適化があります。以前のescchar
関数は、エスケープシーケンスが検出されたかどうかを示すescflag
というフラグを引数として受け取っていました。しかし、このフラグは冗長であり、エスケープされた文字の値(vlong* val
)だけで十分な情報を伝えられると判断された可能性があります。
特に、Unicode文字(Rune)の処理において、エスケープシーケンスと直接的なUnicode文字の区別をより明確にし、コードの簡潔性と効率性を向上させる目的があったと考えられます。Go言語はUnicodeをネイティブにサポートしているため、文字のエンコーディングとデコードはコンパイラの重要な側面です。
前提知識の解説
- 字句解析器 (Lexer/Scanner/Tokenizer): コンパイラのフロントエンドの一部で、ソースコードを読み込み、意味のある最小単位(トークン)に分割する役割を担います。例えば、
"hello"
という文字列は一つの文字列リテラルトークンとして認識されます。 - エスケープシーケンス: 文字列リテラルや文字リテラル内で、特殊な意味を持つ文字を表現するための記法です。例えば、
\n
は改行、\t
はタブ、\"
は二重引用符を表します。 - Unicode / Rune: Unicodeは世界中の文字を統一的に扱うための文字コード体系です。Go言語では、Unicodeコードポイントを表現するために
rune
型が使用されます。rune
はint32
のエイリアスであり、UTF-8でエンコードされた文字列内の単一のUnicodeコードポイントを表します。 vlong
: Go言語の初期のコンパイラコードベースで使われていた型で、おそらく64ビット整数(long long
やint64_t
に相当)を表すものと考えられます。これは、文字のエスケープ値や数値リテラルの値を保持するために使用されます。Runeself
: Go言語の字句解析器における内部的な定数で、おそらくASCII文字の範囲を超えるUnicode文字(Rune)を識別するための閾値として使用されます。UTF-8エンコーディングでは、ASCII文字は1バイトで表現され、それ以外のUnicode文字は複数バイトで表現されます。Runeself
は、その複数バイト文字の開始バイトが持つ特定のビットパターン(例えば、0x80
や0xC0
など)に関連する値である可能性があります。
技術的詳細
このコミットの主要な変更点は、escchar
関数のシグネチャ変更と、それに伴うlex.c
内の文字列および文字リテラル解析ロジックの調整です。
-
escchar
関数のシグネチャ変更:- 変更前:
int escchar(int, int*, vlong*);
- 変更後:
int escchar(int, vlong*);
int* escflg
という引数が削除されました。これは、エスケープシーケンスが検出されたかどうかを示すフラグでしたが、escchar
関数が返すvlong* val
の値自体がエスケープされた文字の値を示すため、このフラグは冗長であると判断されたと考えられます。escchar
関数は、エスケープシーケンスを解析し、その結果の文字コードをval
に格納します。関数が0を返せば成功、非0を返せばエラー(例えば、予期せぬEOFなど)を示します。
- 変更前:
-
lex.c
内の変更:yylex
関数内のescflag
変数が削除されました。- 文字列リテラル(
caseq:
ラベル)の解析において、escchar
の呼び出しがescflag
引数なしに変更されました。 - 以前は
escflag
の真偽によって処理を分岐していましたが、変更後はv >= Runeself
という条件でUnicode文字(Rune)か否かを判断しています。v >= Runeself
の場合、それは複数バイトのUnicode文字であると判断され、runelen
でバイト長を計算し、runetochar
でUTF-8エンコードされたバイト列に変換してバッファに追加します。- そうでない場合(つまり、単一バイトのASCII文字またはエスケープされた単一バイト値)、直接バッファに追加します。
- 文字リテラル(
case '\\'':
ラベル)の解析においても、escchar
の呼び出しがescflag
引数なしに変更されました。
この変更により、字句解析器はエスケープシーケンスの処理をより直接的に行い、特にUnicode文字の扱いが簡潔になりました。escflag
の削除は、コードの複雑性を減らし、可読性を向上させる効果があります。
コアとなるコードの変更箇所
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -387,7 +387,7 @@ void lexinit(void);
char* lexname(int);
long getr(void);\n int getnsc(void);
-int escchar(int, int*, vlong*);
+int escchar(int, vlong*);
int getc(void);
void ungetc(int);
void mkpackage(char*);
src/cmd/gc/lex.c
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -175,7 +175,6 @@ yylex(void)
vlong v;
char *cp;
Rune rune;
- int escflag;
Sym *s;
l0:
@@ -225,19 +224,19 @@ l0:
caseq:
for(;;) {
-\t\t\tif(escchar('"', &escflag, &v))
+\t\t\tif(escchar('"', &v))
break;
-\t\t\tif(escflag) {
-\t\t\t\tcp = remal(cp, c1, 1);
-\t\t\t\tcp[c1++] = v;
-\t\t\t} else {
+\t\t\tif(v >= Runeself) {
// botch - this limits size of runes
rune = v;
c = runelen(rune);
cp = remal(cp, c1, c);
runetochar(cp+c1, &rune);
c1 += c;
+\t\t\t\tcontinue;
}
+\t\t\tcp = remal(cp, c1, 1);
+\t\t\tcp[c1++] = v;
}
goto catem;
@@ -282,9 +281,9 @@ l0:
case '\'':
/* '.' */
-\t\tif(escchar('\'', &escflag, &v))
+\t\tif(escchar('\'', &v))
v = '\''; // allow '''
-\t\tif(!escchar('\'', &escflag, &v)) {
+\t\tif(!escchar('\'', &v)) {
yyerror("missing '");
ungetc(v);
}
@@ -696,13 +695,12 @@ getnsc(void)
int
-escchar(int e, int *escflg, vlong *val)
+escchar(int e, vlong *val)
{
-\tint i, c;
+\tint i;
+\tlong c;
vlong l;
-\t*escflg = 0;
-\
loop:
c = getr();
if(c == '\n') {
@@ -779,7 +777,6 @@ hex:
ungetc(c);
break;
}
-\t*escflg = 1;
*val = l;
return 0;
@@ -796,7 +793,6 @@ oct:
}
if(l > 255)
warn("oct escape value > 255: %d", l);
-\t*escflg = 1;
*val = l;
return 0;
}
コアとなるコードの解説
このコミットの核心は、escchar
関数の役割の明確化と、それを利用するyylex
関数内のロジックの簡素化です。
-
escchar
関数の変更:- 以前は
escflag
というポインタを介してエスケープシーケンスが検出されたかどうかを呼び出し元に伝えていましたが、このフラグは削除されました。 escchar
関数は、エスケープシーケンス(例:\n
,\xHH
,\uHHHH
など)を解析し、その結果得られる文字の数値表現をvlong *val
に格納します。- この変更により、
escchar
は「エスケープシーケンスを解析し、その値を返す」という単一の責任を持つようになり、よりクリーンなAPIになりました。
- 以前は
-
yylex
関数内の文字列リテラル解析 (caseq:
):- 変更前は
escflag
の値をチェックして、エスケープされた文字とそうでない文字で異なる処理を行っていました。 - 変更後は、
escchar
から返されたv
(文字の数値表現)がRuneself
以上かどうかで分岐します。v >= Runeself
は、その文字が複数バイトのUnicode文字(Rune)であることを示唆します。この場合、runelen
でUTF-8エンコード後のバイト数を取得し、runetochar
で実際のバイト列に変換して文字列バッファに追加します。v < Runeself
は、その文字が単一バイトの文字(ASCII文字またはエスケープされた単一バイト値)であることを示唆します。この場合、v
の値をそのまま文字列バッファに追加します。
- このロジックにより、エスケープシーケンスの有無に関わらず、
escchar
が返す値に基づいてUnicode文字と単一バイト文字を適切に処理できるようになりました。
- 変更前は
-
yylex
関数内の文字リテラル解析 (case '\'':
):- こちらも文字列リテラルと同様に、
escchar
の呼び出しからescflag
引数が削除され、より直接的な呼び出しになりました。
- こちらも文字列リテラルと同様に、
全体として、この変更はGo言語のコンパイラが文字エンコーディングとエスケープシーケンスをより効率的かつ統一的に処理するための初期の改善を示しています。
関連リンク
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go言語の字句解析に関する一般的な情報(Go言語のコンパイラ設計に関するドキュメントやブログ記事など)
参考にした情報源リンク
- Go言語の公式ドキュメント(特に言語仕様の文字列リテラルと文字リテラルのセクション)
- コンパイラ設計に関する一般的な書籍やオンラインリソース(字句解析器の設計に関する章)
- Go言語の初期のコミット履歴や設計ドキュメント(もし公開されていれば)
Runeself
のような内部定数に関するGo言語コミュニティの議論や説明(Stack Overflowなど)- UTF-8エンコーディングとUnicodeに関する技術資料