[インデックス 121] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)の字句解析器(lexer)に関連する変更です。具体的には、エスケープシーケンスの処理を担当するescchar
関数のシグネチャと実装が変更されています。
src/cmd/gc/go.h
: Goコンパイラのヘッダーファイルで、escchar
関数のプロトタイプ宣言が含まれています。src/cmd/gc/lex.c
: Goコンパイラの字句解析器の実装ファイルで、escchar
関数の具体的なロジックと、その関数を呼び出すyylex
関数が含まれています。
コミット
commit feb1c77f9c9911ab25aa8826744da99ba109fc6a
Author: Ken Thompson <ken@golang.org>
Date: Fri Jun 6 19:16:18 2008 -0700
cafebabe
SVN=121574
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/feb1c77f9c9911ab25aa8826744da99ba109fc6a
元コミット内容
cafebabe
SVN=121574
このコミットメッセージは非常に簡潔で、具体的な変更内容を直接示していません。"cafebabe"は、Javaのクラスファイルのマジックナンバーとしても知られていますが、ここでは単なるコミットメッセージとして使われているようです。SVN=121574
は、このGitコミットが元々Subversionリポジトリのどのリビジョンに対応するかを示しています。
変更の背景
この変更の主な背景は、Goコンパイラの字句解析器におけるエスケープシーケンスの処理を改善することにあります。特に、escchar
関数の設計を見直し、より堅牢で柔軟な文字値の取得メカニズムを提供することが目的です。
元のescchar
関数は、エスケープされた文字の値を直接ulong
型で返していました。しかし、このアプローチにはいくつかの課題があります。
- エラーハンドリングの曖昧さ:
EOF
のような特殊な値を返すことでエラーを示していましたが、これは有効な文字値と衝突する可能性があり、エラーと正常な文字値を区別するのが難しい場合があります。 - 文字値の範囲の制限:
ulong
型で返せる文字値の範囲には限りがあります。Go言語はUnicodeをサポートしており、Unicodeのコードポイントは0x0
から0x10FFFF
までの範囲を取ります。ulong
が32ビットの場合、0xFFFFFFFF
まで表現できますが、将来的な拡張性や、より明確な値の受け渡し方法が求められた可能性があります。 - C言語の慣習: C言語では、関数が複数の情報を返す必要がある場合(このケースでは「エスケープされた文字の値」と「処理の成否」)、ポインタ引数を通じて値を返し、関数の戻り値で成否やステータスを伝えるのが一般的な慣習です。この変更は、その慣習に沿った設計への移行を示唆しています。
これらの理由から、escchar
関数は、文字値をポインタ引数で渡し、関数の戻り値で処理の成否を返すように変更されました。これにより、エラーハンドリングが明確になり、vlong
型を使用することでより大きな文字値(Unicodeコードポイントなど)を安全に扱えるようになりました。
前提知識の解説
1. 字句解析 (Lexical Analysis)
コンパイラの最初の段階であり、ソースコードを「トークン」と呼ばれる意味のある最小単位に分割するプロセスです。例えば、int x = 10;
というコードは、int
(キーワード)、x
(識別子)、=
(演算子)、10
(リテラル)、;
(区切り文字)といったトークンに分割されます。このコミットで変更されているlex.c
は、この字句解析器の実装の一部です。
2. エスケープシーケンス (Escape Sequences)
文字列リテラルや文字リテラル内で、特殊な意味を持つ文字を表現するために使用される記法です。通常、バックスラッシュ(\
)に続く一連の文字で構成されます。
例:
\n
: 改行\t
: タブ\"
: 二重引用符\\
: バックスラッシュ\xHH
: 16進数エスケープ(HHは16進数2桁)\uHHHH
: Unicodeコードポイントエスケープ(HHHHは16進数4桁)\UHHHHHHHH
: Unicodeコードポイントエスケープ(HHHHHHHHは16進数8桁)
escchar
関数は、これらのエスケープシーケンスを解析し、対応する実際の文字コードを特定する役割を担っています。
3. 文字エンコーディングとUnicode
- ASCII: 英語圏で広く使われた7ビットの文字コード。128種類の文字を表現できます。
- Unicode: 世界中のあらゆる文字を統一的に扱うための文字コード体系。各文字に一意の「コードポイント」を割り当てます。
- UTF-8: Unicodeのコードポイントをバイト列にエンコードするための可変長エンコーディング方式。ASCII互換性があり、多くのWebサイトやシステムで利用されています。Go言語の文字列はUTF-8でエンコードされたUnicodeコードポイントのシーケンスとして扱われます。
4. C言語のデータ型
int
: 整数型。通常32ビット。long
: 整数型。通常32ビットまたは64ビット。ulong
(unsigned long): 符号なし整数型。通常32ビットまたは64ビット。vlong
: このコミットの文脈では、Goコンパイラの内部で使われる、より大きな整数値を表現するための型(おそらく64ビット整数)。Go言語自体にはvlong
という組み込み型はありませんが、コンパイラの実装ではC言語の型やカスタム型が使われることがあります。Unicodeのコードポイントは最大0x10FFFF
であり、これは32ビット整数で表現可能ですが、vlong
を使用することで、より広い範囲の値を扱う柔軟性や、将来的な拡張性を持たせている可能性があります。
5. ポインタ引数による値の受け渡し
C言語では、関数が複数の値を返したい場合や、呼び出し元で変数の値を変更したい場合に、ポインタを引数として渡すことが一般的です。関数内でポインタが指すメモリ位置に値を書き込むことで、呼び出し元はその変更された値を受け取ることができます。
技術的詳細
このコミットの核となる変更は、escchar
関数のシグネチャと、それに伴う呼び出し側の変更です。
escchar
関数のシグネチャ変更
-
変更前:
ulong escchar(int e, int *escflg)
- 戻り値:
ulong
型。エスケープされた文字のコードポイントを直接返していました。エラーの場合はEOF
を返していました。 - 引数:
e
: 終端文字(例: 文字列リテラルの"
、文字リテラルの'
)。escflg
: エスケープシーケンスが検出されたかどうかを示すフラグ(ポインタ渡し)。
- 戻り値:
-
変更後:
int escchar(int e, int *escflg, vlong *val)
- 戻り値:
int
型。処理の成否を示すステータスコードを返します。0
は成功、1
はエラー(例: 文字列中の改行、EOF
)を示します。 - 引数:
e
: 終端文字。escflg
: エスケープシーケンスが検出されたかどうかを示すフラグ(ポインタ渡し)。val
: 新しく追加された引数。エスケープされた文字のコードポイントを格納するためのvlong
型へのポインタ。実際の文字値はこのポインタを通じて呼び出し元に渡されます。
- 戻り値:
escchar
関数の実装変更
- ローカル変数の変更:
ulong c, l;
がint i, c; vlong l;
に変更されました。これにより、一時的な文字値c
はint
になり、16進数/8進数エスケープで累積される値l
はvlong
になりました。 - 戻り値のセマンティクス変更:
- 以前は文字値を直接
return
していましたが、変更後は*val = <文字値>; return 0;
という形式で、val
ポインタを通じて値を渡し、0
を返して成功を示します。 - エラー条件(例: 文字列中の改行、終端文字
e
に到達)の場合、以前はEOF
やc
を直接返していましたが、変更後はreturn 1;
を返してエラーを示します。
- 以前は文字値を直接
- 単純なエスケープシーケンスの処理:
\a
,\b
,\f
などの単純なエスケープシーケンスも、c = <文字値>; *val = c; return 0;
という形式で処理されるようになりました。 - 16進数/8進数エスケープの処理: これらのエスケープシーケンスで解析された値
l
も、*val = l; return 0;
という形式でval
ポインタを通じて渡されます。
yylex
関数(呼び出し側)の変更
yylex
関数は字句解析のメインループであり、文字列リテラルや文字リテラルを解析する際にescchar
を呼び出します。
- ローカル変数の追加:
yylex
関数内にvlong v;
という変数が追加されました。これはescchar
から返される文字値を格納するために使用されます。 escchar
の呼び出し方法の変更:- 変更前:
c = escchar('"', &escflag); if(c == EOF) break;
- 変更後:
if(escchar('"', &escflag, &v)) break;
escchar
の戻り値がステータスコードになったため、if
文でそのステータスをチェックし、エラー(1
)の場合はループを抜けます。- 実際の文字値は
v
に格納されるため、その後の処理でc
の代わりにv
が使用されます。
- 変更前:
- 文字値の使用箇所の変更:
- 文字列リテラル処理 (
caseq
):cp[c1++] = c;
がcp[c1++] = v;
に、rune = c;
がrune = v;
に変更されました。 - 文字リテラル処理 (
case '\''
):yylval.val.vval = c;
がyylval.val.vval = v;
に変更されました。また、エラーハンドリングのロジックもescchar
の新しい戻り値に合わせて調整されました。
- 文字列リテラル処理 (
これらの変更により、escchar
関数はより明確なエラー報告メカニズムを持ち、vlong
型を使用することで、より広範な文字コード(特にUnicodeの全範囲)を安全かつ効率的に処理できるようになりました。これは、Go言語がUnicodeを第一級でサポートする上で重要な基盤となる変更です。
コアとなるコードの変更箇所
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -387,7 +387,7 @@ void lexinit(void);\n char* lexname(int);\n long getr(void);\n int getnsc(void);\n-ulong escchar(int, int*);\n+int escchar(int, int*, vlong*);\n int getc(void);\n void ungetc(int);\n void mkpackage(char*);\
escchar
関数のプロトタイプ宣言が変更され、戻り値がulong
からint
に、引数にvlong*
が追加されました。
src/cmd/gc/lex.c
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -171,7 +171,8 @@ cannedimports(void)\n long\n yylex(void)\n {\n-\tulong c, c1;\n+\tint c, c1;\n+\tvlong v;\n \tchar *cp;\n \tRune rune;\n \tint escflag;\
yylex
関数内で、ulong c
がint c
に変更され、vlong v;
が新しく宣言されました。
@@ -224,14 +225,14 @@ l0:\n \n \tcaseq:\n \t\tfor(;;) {\n-\t\t\tc = escchar(\'\"\', &escflag);\n-\t\t\tif(c == EOF)\n+\t\t\tif(escchar(\'\"\', &escflag, &v))\n \t\t\t\tbreak;\n \t\t\tif(escflag) {\n \t\t\t\tcp = remal(cp, c1, 1);\n-\t\t\t\tcp[c1++] = c;\n+\t\t\t\tcp[c1++] = v;\n \t\t\t} else {\n-\t\t\t\trune = c;\n+\t\t\t\trune = v;\n \t\t\t\tc = runelen(rune);\n \t\t\t\tcp = remal(cp, c1, c);\n \t\t\t\trunetochar(cp+c1, &rune);\
文字列リテラル処理において、escchar
の呼び出しと、その結果の文字値の利用方法がv
変数を使用するように変更されました。
@@ -281,15 +282,13 @@ l0:\n \n \tcase \'\\\'\':\n \t\t/* \'.\' */\n-\t\tc = escchar(\'\\\'\', &escflag);\n-\t\tif(c == EOF)\n-\t\t\tc = \'\\\'\';\n-\t\tc1 = escchar(\'\\\'\', &escflag);\n-\t\tif(c1 != EOF) {\n+\t\tif(escchar(\'\\\'\', &escflag, &v))\n+\t\t\tv = \'\\\'\';\t// allow \'\'\'\n+\t\tif(!escchar(\'\\\'\', &escflag, &v)) {\n \t\t\tyyerror(\"missing \'\");\n-\t\t\tungetc(c1);\n+\t\t\tungetc(v);\n \t\t}\n-\t\tyylval.val.vval = c;\n+\t\tyylval.val.vval = v;\n \t\tyylval.val.ctype = CTINT;\n \t\tDBG(\"lex: codepoint literal\\n\");\n \t\treturn LLITERAL;\
文字リテラル処理においても、escchar
の呼び出しと、その結果の文字値の利用方法がv
変数を使用するように変更されました。
@@ -696,11 +695,11 @@ getnsc(void)\n }\n \n \n-ulong\n-escchar(int e, int *escflg)\n+int\n+escchar(int e, int *escflg, vlong *val)\n {\n-\tulong c, l;\n-\tint i;\n+\tint i, c;\n+\tvlong l;\
escchar
関数の実装において、ローカル変数の型が変更され、新しいvlong *val
引数が追加されました。
@@ -708,13 +707,15 @@ loop:\n \tc = getr();\n \tif(c == \'\\n\') {\n \t\tyyerror(\"newline in string\");\n-\t\treturn EOF;\n+\t\treturn 1;\n \t}\n \tif(c != \'\\\\\') {\n \t\tif(c == e)\n-\t\t\tc = EOF;\n-\t\treturn c;\n+\t\t\treturn 1;\n+\t\t*val = c;\n+\t\treturn 0;\
escchar
関数の内部ロジックで、文字値の返し方が*val = c; return 0;
の形式に変更され、エラーの場合はreturn 1;
となりました。
@@ -742,20 +743,21 @@ loop:\n \tcase \'7\':\n \t\tgoto oct;\n \n-\tcase \'a\': return \'\\a\';\n-\tcase \'b\': return \'\\b\';\n-\tcase \'f\': return \'\\f\';\n-\tcase \'n\': return \'\\n\';\n-\tcase \'r\': return \'\\r\';\n-\tcase \'t\': return \'\\t\';\n-\tcase \'v\': return \'\\v\';\n-\tcase \'\\\\\': return \'\\\\\';\n+\tcase \'a\': c = \'\\a\'; break;\n+\tcase \'b\': c = \'\\b\'; break;\n+\tcase \'f\': c = \'\\f\'; break;\n+\tcase \'n\': c = \'\\n\'; break;\n+\tcase \'r\': c = \'\\r\'; break;\n+\tcase \'t\': c = \'\\t\'; break;\n+\tcase \'v\': c = \'\\v\'; break;\n+\tcase \'\\\\\': c = \'\\\\\'; break;\n \n \tdefault:\n \t\tif(c != e)\n \t\t\twarn(\"unknown escape sequence: %c\", c);\n \t}\n-\treturn c;\n+\t*val = c;\n+\treturn 0;\
単純なエスケープシーケンスの処理も、*val = c; return 0;
の形式に変更されました。
@@ -778,7 +780,8 @@ hex:\n \t\tbreak;\n \t}\n \t*escflg = 1;\n-\treturn l;\n+\t*val = l;\n+\treturn 0;\
16進数エスケープの処理も、*val = l; return 0;
の形式に変更されました。
@@ -794,7 +797,8 @@ oct:\n \tif(l > 255)\n \t\twarn(\"oct escape value > 255: %d\", l);\n \t*escflg = 1;\n-\treturn l;\n+\t*val = l;\n+\treturn 0;\
8進数エスケープの処理も、*val = l; return 0;
の形式に変更されました。
コアとなるコードの解説
このコミットの核心は、escchar
関数のインターフェースと実装の変更にあります。
-
escchar
関数の役割の明確化:- 変更前は、
escchar
関数がエスケープされた文字の値を直接返すことで、その値が有効な文字なのか、それともエラーを示すEOF
なのかを呼び出し側で判断する必要がありました。 - 変更後は、
escchar
関数は処理の成否(成功なら0
、エラーなら1
)を戻り値で明確に示し、実際の文字値は新しく追加されたvlong *val
ポインタを通じて呼び出し元に渡されるようになりました。これにより、関数の役割が「エスケープシーケンスを解析し、結果をポインタで渡し、成否を戻り値で伝える」と明確になりました。
- 変更前は、
-
vlong
型の導入とUnicode対応の強化:escchar
が返す文字値を格納する型がulong
からvlong
(ポインタ経由)に変更されたことは重要です。Go言語はUnicodeを完全にサポートしており、Unicodeのコードポイントは0x0
から0x10FFFF
までの範囲を取ります。これは32ビットのulong
で表現可能ですが、vlong
を使用することで、より大きな値を扱う柔軟性や、将来的な拡張性を持たせている可能性があります。また、C言語の慣習に沿ったポインタ渡しにより、より大きなデータ型を効率的に扱うことができます。
-
エラーハンドリングの改善:
- 以前の
EOF
を返す方式は、EOF
が有効な文字値と衝突する可能性があり、エラーの判別が曖昧になることがありました。 - 新しい方式では、
escchar
が1
を返すことで明確にエラーを通知するため、呼び出し側はより堅牢なエラーハンドリングを実装できます。例えば、文字列リテラル中に改行があった場合など、不正なエスケープシーケンスを検出した際に、yylex
関数が適切に処理を中断できるようになりました。
- 以前の
-
コードの可読性と保守性の向上:
- 関数の戻り値がステータス、ポインタ引数が結果という明確な役割分担により、コードの意図がより分かりやすくなりました。
- これにより、将来的に
escchar
関数の内部ロジックが変更された場合でも、インターフェースが安定しているため、呼び出し側の変更を最小限に抑えることができます。
これらの変更は、Goコンパイラの字句解析器が、より正確で堅牢なエスケープシーケンス処理を行い、Go言語のUnicodeサポートを確実にするための重要なステップと言えます。
関連リンク
- Go言語の仕様: https://go.dev/ref/spec (特に "String literals" と "Rune literals" のセクション)
- Go言語のコンパイラに関する情報: https://go.dev/doc/compiler
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- C言語のポインタに関する一般的な情報
- コンパイラの字句解析に関する一般的な情報
- UnicodeとUTF-8に関する一般的な情報