[インデックス 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に関する技術資料