[インデックス 1472] ファイルの概要
このコミットは、Go言語の初期開発段階におけるpretty
パッケージ(おそらくはGoコードの構文解析、スキャン、または整形に関連する内部ツール)に対する重要な改善を含んでいます。主な変更点は以下の3つです。
- 新しい文字定義の採用: スキャナーがUnicode文字を正しく識別できるように、
is_letter
関数の定義が更新されました。これにより、Go言語の識別子としてUnicode文字が利用可能になる基盤が強化されました。 - UTF-8文字を含むエラー列報告のバグ修正: ソースコード内のUTF-8文字の存在下で、エラーメッセージの列番号が誤って報告されるバグが修正されました。これにより、コンパイラやツールのエラー報告の精度が向上しました。
- アサーション失敗の修正: パーサーにおける特定のアサーション失敗が修正され、不正な式が宣言されるのを防ぐことで、パーサーの堅牢性が向上しました。
コミット
commit a0c709bef8516b00c76b1d0350138fdf21f60904
Author: Robert Griesemer <gri@golang.org>
Date: Wed Jan 14 15:19:34 2009 -0800
- use new letter definition for pretty
- fixed a bug with error column reporting in the presence of utf-8 chars
- fixed an assertion failure
R=r
OCL=22762
CL=22762
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a0c709bef8516b00c76b1d0350138fdf21f60904
元コミット内容
このコミットは、Go言語のpretty
パッケージにおける以下の3つの主要な問題を解決します。
pretty
パッケージで新しい文字定義を使用するように変更。- UTF-8文字が存在する場合のエラー列報告のバグを修正。
- アサーション失敗のバグを修正。
変更の背景
このコミットは、Go言語がまだ初期開発段階にあった2009年に行われました。当時のGo言語は、その設計と実装が活発に進められており、特にUnicodeサポートやエラー報告の正確性といった基本的な機能の改善が継続的に行われていました。
- Unicode識別子のサポート: 多くのプログラミング言語と同様に、Goも国際化に対応するため、識別子にASCII以外のUnicode文字を使用できるようにする必要がありました。この変更は、スキャナーがUnicode文字を正しく「文字」として認識するための基盤を築くものです。
- 正確なエラー報告: コンパイラやリンターなどのツールにとって、エラーメッセージの正確性は開発者のデバッグ体験に直結します。特に、UTF-8のような可変長エンコーディングの文字がソースコードに含まれる場合、バイト数と文字数(ルーン数)が異なるため、列番号の計算が複雑になります。このコミットは、この問題に対処し、より正確なエラー位置情報を提供することを目的としています。
- パーサーの堅牢性: コンパイラのパーサーは、不正な入力に対してもクラッシュすることなく、適切にエラーを報告し続ける必要があります。特定のアサーション失敗は、パーサーが予期しない状態に陥る可能性を示唆しており、その修正はパーサー全体の安定性と堅牢性を高めるために不可欠でした。
これらの変更は、Go言語のコンパイラおよびツールチェーンの品質と国際化対応を向上させるための、初期ながらも重要なステップでした。
前提知識の解説
- Go言語の
pretty
パッケージ: コミットメッセージやファイルパス(usr/gri/pretty/
)から推測されるに、これはGo言語のコンパイラまたはツールチェーンの一部であり、ソースコードの構文解析、スキャン、または整形(pretty-printing)に関連する機能を提供していたと考えられます。Go言語の初期のコードベースでは、このような実験的なパッケージが多数存在しました。 - UTF-8エンコーディング: Unicode文字をバイト列にエンコードするための可変長文字エンコーディングです。ASCII文字は1バイトで表現されますが、それ以外の多くの文字は2バイト以上で表現されます。この特性が、バイトオフセットと文字(ルーン)オフセットのずれを引き起こし、正確な列番号の計算を困難にします。
- ルーン (Rune): Go言語における「ルーン」は、Unicodeのコードポイントを表す
int32
型のエイリアスです。UTF-8でエンコードされた文字列を扱う際、バイト列ではなくルーン単位で処理することで、文字の正確な数を数えたり、文字単位での操作を行ったりできます。 - スキャナー (Scanner): プログラミング言語のコンパイラにおける最初のフェーズで、ソースコードの文字列をトークン(キーワード、識別子、演算子など)のストリームに変換する役割を担います。
- パーサー (Parser): スキャナーによって生成されたトークンのストリームを受け取り、言語の文法規則に従って抽象構文木(AST)を構築する役割を担います。
- アサーション (Assertion): プログラムの特定の時点で、ある条件が真であると仮定し、その条件が満たされない場合にプログラムを停止させるデバッグ手法です。開発段階で予期せぬ状態を早期に発見するために使用されます。
unicode
パッケージ: Go言語の標準ライブラリの一部で、Unicode文字のプロパティ(文字種、大文字/小文字変換など)を扱うための機能を提供します。utf8
パッケージ: Go言語の標準ライブラリの一部で、UTF-8エンコードされたバイト列をルーンとして扱うための機能を提供します。例えば、バイト列中のルーン数をカウントするRuneCountInString
関数などがあります。
技術的詳細
1. UTF-8文字を含むエラー列報告の修正
usr/gri/pretty/compilation.go
のErrorHandler.LineCol
関数は、ソースコード内の特定の位置(pos
)に対応する行番号と列番号を計算する役割を担っていました。元の実装では、列番号をpos - lpos
(lpos
は行の開始バイトオフセット)として計算していました。これは、ASCII文字のみを想定している場合には問題ありませんが、UTF-8文字(マルチバイト文字)が含まれる場合、1文字が複数バイトを占めるため、バイトオフセットと実際の文字の列が一致しなくなります。
このコミットでは、utf8
パッケージをインポートし、列番号の計算にutf8.RuneCountInString
関数を使用するように変更しました。
utf8.RuneCountInString(src, lpos, pos - lpos)
は、src
文字列のlpos
からpos
までのバイト範囲に含まれるルーン(Unicodeコードポイント)の数を正確にカウントします。これにより、UTF-8文字の存在下でも、エラー報告における列番号が正しく表示されるようになりました。
2. 新しい文字定義の採用(Unicode識別子のサポート)
usr/gri/pretty/scanner.go
のis_letter
関数は、Go言語の識別子として有効な文字を判定するために使用されていました。元の実装では、ASCIIの小文字、大文字、アンダースコア、およびコードポイントが128以上の文字を「文字」としていました。これは非常に大雑把な定義であり、Unicodeの複雑な文字体系を正確にカバーしていませんでした。
このコミットでは、unicode
パッケージをインポートし、is_letter
関数がunicode.IsLetter(ch)
を呼び出すように変更されました。unicode.IsLetter
関数は、Unicode標準で定義されている「文字」のプロパティに基づいて、与えられたルーンが文字であるかどうかを正確に判定します。これにより、Go言語の識別子として、世界中の様々な言語の文字(例: ä
, ö
, ü
, Á
, Ø
, Å
, ƒ
, ß
など)が正しく認識されるようになり、国際化対応が大きく前進しました。
3. アサーション失敗の修正
usr/gri/pretty/parser.go
のParser.DeclareInScope
関数は、スコープ内でオブジェクトを宣言する役割を担っていました。元のコードでは、x.tok
がScanner.ILLEGAL
(不正なトークン)である場合でも、無条件にx.obj
にアクセスしようとしていました。これは、x
が不正な式である場合に、x.obj
がnil
であるか、または予期しない状態である可能性があり、その後のassert
呼び出しや他の操作でアサーション失敗を引き起こす原因となっていました。
このコミットでは、if x.tok != Scanner.ILLEGAL
という条件チェックが追加されました。これにより、x
が不正な式である場合には、オブジェクトの宣言処理をスキップし、アサーション失敗を防ぐことができます。これは、パーサーがより堅牢になり、不正な入力に対しても安定して動作するための重要な修正です。
コアとなるコードの変更箇所
usr/gri/pretty/compilation.go
--- a/usr/gri/pretty/compilation.go
+++ b/usr/gri/pretty/compilation.go
@@ -4,13 +4,16 @@
package Compilation
-import "array"\n -import OS "os"\n -import Platform "platform"\n -import Scanner "scanner"\n -import Parser "parser"\n -import AST "ast"\n -import TypeChecker "typechecker"\n +import (\n +\t"array";\n +\t"utf8";\n +\tOS "os";\n +\tPlatform "platform";\n +\tScanner "scanner";\n +\tParser "parser";\n +\tAST "ast";\n +\tTypeChecker "typechecker";\n +)\n
func assert(b bool) {
@@ -67,7 +70,7 @@ func (h *ErrorHandler) LineCol(pos int) (line, col int) {
}\n }\n
-\treturn line, pos - lpos;\n +\treturn line, utf8.RuneCountInString(src, lpos, pos - lpos);\n }
usr/gri/pretty/parser.go
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -167,15 +167,17 @@ func (P *Parser) DeclareInScope(scope *AST.Scope, x *AST.Expr, kind int) {
if P.scope_lev < 0 {
panic("cannot declare objects in other packages");
}\n -\tobj := x.obj;\n -\tassert(x.tok == Scanner.IDENT && obj.kind == AST.NONE);\n -\tobj.kind = kind;\n -\tobj.pnolev = P.scope_lev;\n -\tif scope.LookupLocal(obj.ident) != nil {\n -\t\tP.Error(obj.pos, `\"` + obj.ident + `\" is declared already`);\n -\t\treturn; // don\'t insert it into the scope
-\t}\n -\tscope.Insert(obj);\n +\tif x.tok != Scanner.ILLEGAL { // ignore bad exprs
+\t\tobj := x.obj;\n +\t\tassert(x.tok == Scanner.IDENT && obj.kind == AST.NONE);\n +\t\tobj.kind = kind;\n +\t\tobj.pnolev = P.scope_lev;\n +\t\tif scope.LookupLocal(obj.ident) != nil {\n +\t\t\tP.Error(obj.pos, `\"` + obj.ident + `\" is declared already`);\n +\t\t\treturn; // don\'t insert it into the scope
+\t\t}\n +\t\tscope.Insert(obj);\n +\t}\n }
usr/gri/pretty/scanner.go
--- a/usr/gri/pretty/scanner.go
+++ b/usr/gri/pretty/scanner.go
@@ -5,6 +5,7 @@
package Scanner
import "utf8"\n +import "unicode"\n import Utils "utils"\n
@@ -254,7 +255,9 @@ func init() {
func is_letter(ch int) bool {
-\treturn \'a\' <= ch && ch <= \'z\' || \'A\' <= ch && ch <= \'Z\' || ch == \'_\' || ch >= 128 ;\n +\treturn\n +\t\t\'a\' <= ch && ch <= \'z\' || \'A\' <= ch && ch <= \'Z\' || // common case
+\t\tch == \'_\' || unicode.IsLetter(ch);\n }
usr/gri/pretty/selftest2.go
--- a/usr/gri/pretty/selftest2.go
+++ b/usr/gri/pretty/selftest2.go
@@ -52,6 +52,12 @@ var (
)
+var (\n +\t// Unicode identifiers\n +\tä, ö, ü, Á, Ø, Å, ƒ, ß int;\n +)\n +\n +\n func d0() {
var (\n \ta string;\n```
## コアとなるコードの解説
### `compilation.go` の変更
* **`import "utf8"` の追加**: UTF-8文字のルーン数を正確にカウントするために、`utf8`パッケージがインポートされました。
* **`LineCol` 関数の変更**:
* 変更前: `return line, pos - lpos;`
* 変更後: `return line, utf8.RuneCountInString(src, lpos, pos - lpos);`
* この変更により、列番号の計算がバイトオフセットからルーン数に基づくものに変わり、UTF-8文字を含むソースコードでも正確な列番号が報告されるようになりました。
### `parser.go` の変更
* **`DeclareInScope` 関数の変更**:
* 既存の宣言ロジック全体が `if x.tok != Scanner.ILLEGAL { ... }` ブロックで囲まれました。
* この変更により、パーサーが不正なトークン(`Scanner.ILLEGAL`)を持つ式を処理しようとした際に、アサーション失敗を引き起こす可能性が排除されました。不正な式は無視され、パーサーの堅牢性が向上します。
### `scanner.go` の変更
* **`import "unicode"` の追加**: Unicode文字のプロパティを判定するために、`unicode`パッケージがインポートされました。
* **`is_letter` 関数の変更**:
* 変更前: `return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 128 ;`
* 変更後: `return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || unicode.IsLetter(ch);`
* この変更により、`is_letter`関数は、ASCIIの英字とアンダースコアに加えて、`unicode.IsLetter`関数によって定義されるすべてのUnicode文字を「文字」として認識するようになりました。これにより、Go言語の識別子としてUnicode文字が使用できるようになります。
### `selftest2.go` の変更
* **Unicode識別子のテストケース追加**:
* `var ( // Unicode identifiers ä, ö, ü, Á, Ø, Å, ƒ, ß int; )`
* このテストケースは、`scanner.go`の`is_letter`関数の変更が正しく機能し、Go言語がUnicode識別子を認識できるようになったことを検証するために追加されました。
## 関連リンク
* Go言語の公式ウェブサイト: [https://go.dev/](https://go.dev/)
* Go言語の`unicode`パッケージドキュメント: [https://pkg.go.dev/unicode](https://pkg.go.dev/unicode)
* Go言語の`utf8`パッケージドキュメント: [https://pkg.go.dev/unicode/utf8](https://pkg.go.dev/unicode/utf8)
## 参考にした情報源リンク
* Go言語のソースコード(GitHub): [https://github.com/golang/go](https://github.com/golang/go)
* Unicode標準: [https://www.unicode.org/](https://www.unicode.org/)
* UTF-8に関するWikipedia記事: [https://ja.wikipedia.org/wiki/UTF-8](https://ja.wikipedia.org/wiki/UTF-8)