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

[インデックス 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型で返していました。しかし、このアプローチにはいくつかの課題があります。

  1. エラーハンドリングの曖昧さ: EOFのような特殊な値を返すことでエラーを示していましたが、これは有効な文字値と衝突する可能性があり、エラーと正常な文字値を区別するのが難しい場合があります。
  2. 文字値の範囲の制限: ulong型で返せる文字値の範囲には限りがあります。Go言語はUnicodeをサポートしており、Unicodeのコードポイントは0x0から0x10FFFFまでの範囲を取ります。ulongが32ビットの場合、0xFFFFFFFFまで表現できますが、将来的な拡張性や、より明確な値の受け渡し方法が求められた可能性があります。
  3. 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関数の実装変更

  1. ローカル変数の変更: ulong c, l;int i, c; vlong l;に変更されました。これにより、一時的な文字値cintになり、16進数/8進数エスケープで累積される値lvlongになりました。
  2. 戻り値のセマンティクス変更:
    • 以前は文字値を直接returnしていましたが、変更後は*val = <文字値>; return 0;という形式で、valポインタを通じて値を渡し、0を返して成功を示します。
    • エラー条件(例: 文字列中の改行、終端文字eに到達)の場合、以前はEOFcを直接返していましたが、変更後はreturn 1;を返してエラーを示します。
  3. 単純なエスケープシーケンスの処理: \a, \b, \fなどの単純なエスケープシーケンスも、c = <文字値>; *val = c; return 0;という形式で処理されるようになりました。
  4. 16進数/8進数エスケープの処理: これらのエスケープシーケンスで解析された値lも、*val = l; return 0;という形式でvalポインタを通じて渡されます。

yylex関数(呼び出し側)の変更

yylex関数は字句解析のメインループであり、文字列リテラルや文字リテラルを解析する際にesccharを呼び出します。

  1. ローカル変数の追加: yylex関数内にvlong v;という変数が追加されました。これはesccharから返される文字値を格納するために使用されます。
  2. esccharの呼び出し方法の変更:
    • 変更前: c = escchar('"', &escflag); if(c == EOF) break;
    • 変更後: if(escchar('"', &escflag, &v)) break;
      • esccharの戻り値がステータスコードになったため、if文でそのステータスをチェックし、エラー(1)の場合はループを抜けます。
      • 実際の文字値はvに格納されるため、その後の処理でcの代わりにvが使用されます。
  3. 文字値の使用箇所の変更:
    • 文字列リテラル処理 (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 cint 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関数のインターフェースと実装の変更にあります。

  1. escchar関数の役割の明確化:

    • 変更前は、escchar関数がエスケープされた文字の値を直接返すことで、その値が有効な文字なのか、それともエラーを示すEOFなのかを呼び出し側で判断する必要がありました。
    • 変更後は、escchar関数は処理の成否(成功なら0、エラーなら1)を戻り値で明確に示し、実際の文字値は新しく追加されたvlong *valポインタを通じて呼び出し元に渡されるようになりました。これにより、関数の役割が「エスケープシーケンスを解析し、結果をポインタで渡し、成否を戻り値で伝える」と明確になりました。
  2. vlong型の導入とUnicode対応の強化:

    • esccharが返す文字値を格納する型がulongからvlong(ポインタ経由)に変更されたことは重要です。Go言語はUnicodeを完全にサポートしており、Unicodeのコードポイントは0x0から0x10FFFFまでの範囲を取ります。これは32ビットのulongで表現可能ですが、vlongを使用することで、より大きな値を扱う柔軟性や、将来的な拡張性を持たせている可能性があります。また、C言語の慣習に沿ったポインタ渡しにより、より大きなデータ型を効率的に扱うことができます。
  3. エラーハンドリングの改善:

    • 以前のEOFを返す方式は、EOFが有効な文字値と衝突する可能性があり、エラーの判別が曖昧になることがありました。
    • 新しい方式では、escchar1を返すことで明確にエラーを通知するため、呼び出し側はより堅牢なエラーハンドリングを実装できます。例えば、文字列リテラル中に改行があった場合など、不正なエスケープシーケンスを検出した際に、yylex関数が適切に処理を中断できるようになりました。
  4. コードの可読性と保守性の向上:

    • 関数の戻り値がステータス、ポインタ引数が結果という明確な役割分担により、コードの意図がより分かりやすくなりました。
    • これにより、将来的にescchar関数の内部ロジックが変更された場合でも、インターフェースが安定しているため、呼び出し側の変更を最小限に抑えることができます。

これらの変更は、Goコンパイラの字句解析器が、より正確で堅牢なエスケープシーケンス処理を行い、Go言語のUnicodeサポートを確実にするための重要なステップと言えます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • C言語のポインタに関する一般的な情報
  • コンパイラの字句解析に関する一般的な情報
  • UnicodeとUTF-8に関する一般的な情報