[インデックス 13771] ファイルの概要
このコミットは、Go言語のgo/scanner
パッケージにおいて、ソースファイルの先頭に存在する可能性のあるバイトオーダーマーク(BOM)をスキップする機能を追加し、またUnicodeエスケープシーケンスの検証におけるサロゲートペアの範囲指定を修正(表記の統一)するものです。これにより、BOM付きのGoソースファイルが正しく解析されるようになり、Unicodeエスケープシーケンスの検証コードの可読性が向上します。
コミット
commit d5ab44e2fe6fbc11528c7b3bb0f306167d8f1b1b
Author: Robert Griesemer <gri@golang.org>
Date: Fri Sep 7 13:56:31 2012 -0700
go/scanner: skip first character if it's a BOM
R=r
CC=golang-dev
https://golang.org/cl/6490095
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d5ab44e2fe6fbc11528c7b3bb0f306167d8f1b1b
元コミット内容
このコミットの主な目的は、Go言語のソースコードを字句解析するgo/scanner
パッケージが、ファイルの先頭に付加されているバイトオーダーマーク(BOM)を適切に処理できるようにすることです。具体的には、BOMが存在する場合、それを無視して実際のコードの解析を開始するように変更されています。また、Unicodeエスケープシーケンスの検証ロジックにおいて、サロゲートペアの範囲を示す16進数リテラルの表記を小文字から大文字に統一する変更も含まれています。
変更の背景
BOMのスキップ
一部のテキストエディタやシステムでは、UTF-8エンコードされたファイルの先頭にバイトオーダーマーク(BOM)を付加することがあります。BOMは、ファイルのエンコーディングやバイト順を示すための特別なシーケンスですが、UTF-8においては必須ではなく、むしろ互換性の問題を引き起こすことがあります。Go言語のコンパイラやツールチェーンは、通常BOMなしのUTF-8ファイルを想定しています。もしBOMが存在すると、スキャナーがそれを通常の文字として解釈してしまい、字句解析エラーや予期せぬ動作を引き起こす可能性がありました。この変更は、BOM付きのGoソースファイルがGoツールチェーンによって正しく処理されるようにするために導入されました。
Unicodeエスケープシーケンスの検証
Go言語では、文字列リテラルや文字リテラル内で\uXXXX
や\UXXXXXXXX
のようなUnicodeエスケープシーケンスを使用してUnicode文字を表現できます。これらのエスケープシーケンスは、有効なUnicodeコードポイントを表す必要があります。Unicodeには、サロゲートペアと呼ばれる特定の範囲(U+D800からU+DFFF)があり、これらは単独では有効な文字ではなく、UTF-16エンコーディングで補助文字(サロゲートペアで表現される文字)を表現するために使用されます。Go言語の仕様では、これらのサロゲートペアの範囲にあるコードポイントを直接エスケープシーケンスで指定することは許可されていません。このコミットでは、その検証ロジック自体に変更はありませんが、コード内の16進数リテラルの表記(0xd800
から0xD800
へ)を統一し、可読性を向上させています。これは機能的な変更ではなく、コードスタイルの一貫性を保つためのものです。
前提知識の解説
バイトオーダーマーク (BOM)
バイトオーダーマーク(BOM)は、Unicodeテキストファイルの先頭に付加される特別なバイトシーケンスで、ファイルのエンコーディング(UTF-8, UTF-16, UTF-32など)と、UTF-16やUTF-32の場合のバイト順(ビッグエンディアンまたはリトルエンディアン)を示すために使用されます。
- UTF-8のBOM:
EF BB BF
(16進数)。UTF-8ではバイト順の概念がないため、BOMは必須ではありません。しかし、一部のWindowsアプリケーションなどでUTF-8ファイルにBOMを付加する習慣があります。BOMが存在すると、それを認識しないパーサーはファイルの先頭に3バイトのゴミデータがあると解釈してしまう可能性があります。
Go言語のgo/scanner
パッケージ
go/scanner
パッケージは、Go言語のソースコードを字句解析(lexical analysis)するためのパッケージです。字句解析とは、ソースコードの文字列を、言語の構成要素であるトークン(キーワード、識別子、演算子、リテラルなど)のストリームに変換するプロセスです。このパッケージは、Goコンパイラやその他のGoツール(go fmt
など)の基盤として使用されます。
Scanner.Init()
: スキャナーを初期化し、解析対象のソースコードを設定するメソッドです。通常、このメソッドが呼び出された後、字句解析が開始されます。s.ch
: スキャナーが現在処理している文字(rune)を保持するフィールドです。s.next()
: スキャナーの内部ポインタを次の文字に進め、s.ch
を更新するメソッドです。
Unicodeとサロゲートペア
Unicodeは、世界中のあらゆる文字を統一的に扱うための文字コード標準です。各文字には一意の「コードポイント」が割り当てられています。
- コードポイント: Unicode文字を一意に識別する整数値(例:
U+0041
は'A')。 - サロゲートペア: UTF-16エンコーディングにおいて、基本多言語面(BMP、U+0000からU+FFFF)に含まれない補助文字(U+10000からU+10FFFF)を表現するために使用される2つの16ビット値の組み合わせです。サロゲートペアは、高サロゲート(U+D800からU+DBFF)と低サロゲート(U+DC00からU+DFFF)の2つの範囲のコードポイントから構成されます。これらの範囲のコードポイントは、単独では有効なUnicode文字ではありません。
Go言語の仕様では、Unicodeエスケープシーケンス(\uXXXX
や\UXXXXXXXX
)で直接サロゲートペアの範囲のコードポイントを指定することは、無効なUnicodeコードポイントとして扱われます。これは、GoがUTF-8をネイティブに扱い、サロゲートペアはUTF-16のエンコーディングメカニズムに特有のものであるためです。
技術的詳細
BOMのスキップ処理
src/pkg/go/scanner/scanner.go
のScanner.Init
メソッドに以下のコードが追加されました。
s.next()
if s.ch == '\uFEFF' {
s.next() // ignore BOM
}
s.next()
:Scanner.Init
メソッドの初期化処理の一部として、まず最初の文字を読み込み、s.ch
に格納します。if s.ch == '\uFEFF'
: 読み込んだ最初の文字がUnicodeのBOM文字(U+FEFF)であるかどうかをチェックします。Go言語では、文字リテラル'\uFEFF'
はU+FEFFのUnicodeコードポイントを表します。s.next() // ignore BOM
: もし最初の文字がBOMであれば、再度s.next()
を呼び出すことで、スキャナーの内部ポインタをBOMの次の文字に進めます。これにより、BOMは字句解析の対象から除外され、実質的に無視されます。
この変更により、BOM付きのGoソースファイルがgo/scanner
によって正しく処理され、BOMが構文エラーを引き起こすことがなくなります。
Unicodeエスケープシーケンス検証の表記修正
src/pkg/go/scanner/scanner.go
のScanner.scanEscape
メソッドにおいて、以下の変更が行われました。
- if x > max || 0xd800 <= x && x < 0xe000 {
+ if x > max || 0xD800 <= x && x < 0xE000 {
この変更は、0xd800
と0xe000
という16進数リテラルの小文字表記を、それぞれ0xD800
と0xE000
という大文字表記に変更したものです。Go言語では16進数リテラルの大文字・小文字は区別されないため、この変更は機能的な影響を与えません。これは、コードのスタイルガイドラインに合わせる、または単に可読性を向上させるためのクリーンアップ作業と考えられます。
この条件式は、エスケープされたUnicodeコードポイントx
が以下のいずれかの条件を満たす場合にエラーを報告します。
x > max
:x
が許容される最大値(例えば\uXXXX
の場合はU+FFFF、\UXXXXXXXX
の場合はU+10FFFF)を超えている場合。0xD800 <= x && x < 0xE000
:x
がUnicodeのサロゲートペアの範囲(U+D800からU+DFFF)にある場合。Go言語では、これらのコードポイントを直接エスケープシーケンスで指定することは無効とされています。
コアとなるコードの変更箇所
diff --git a/src/pkg/go/scanner/scanner.go b/src/pkg/go/scanner/scanner.go
index c213161c47..3322c58b33 100644
--- a/src/pkg/go/scanner/scanner.go
+++ b/src/pkg/go/scanner/scanner.go
@@ -125,6 +125,9 @@ func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode\n \ts.ErrorCount = 0\n \n \ts.next()\n+\tif s.ch == '\uFEFF' {\n+\t\ts.next() // ignore BOM\n+\t}\n }\n \n func (s *Scanner) error(offs int, msg string) {\
@@ -390,7 +393,7 @@ func (s *Scanner) scanEscape(quote rune) {\
\tfor ; i > 0 && s.ch != quote && s.ch >= 0; i-- {\
\t\ts.next()\n \t}\
-\tif x > max || 0xd800 <= x && x < 0xe000 {\
+\tif x > max || 0xD800 <= x && x < 0xE000 {\
\t\ts.error(offs, "escape sequence is invalid Unicode code point")\
\t}\
}\
コアとなるコードの解説
Scanner.Init
メソッドにおけるBOMスキップ
Scanner.Init
メソッドは、スキャナーがソースファイルを読み込み始める際に呼び出される初期化関数です。
変更前は、単に最初の文字を読み込むだけでした。
変更後は、最初の文字を読み込んだ後、その文字が'\uFEFF'
(UnicodeのBOM文字)であるかをチェックします。もしBOMであれば、s.next()
を再度呼び出すことで、スキャナーの読み取り位置をBOMの次の文字に移動させます。これにより、BOMは字句解析の対象から外され、Goのコードとして認識される部分から解析が開始されます。
Scanner.scanEscape
メソッドにおけるUnicodeコードポイント範囲の表記修正
Scanner.scanEscape
メソッドは、文字列リテラルや文字リテラル内のUnicodeエスケープシーケンス(例: \uXXXX
)を解析し、その値が有効なUnicodeコードポイントであるかを検証する役割を担っています。
このメソッド内の条件式if x > max || 0xd800 <= x && x < 0xe000
は、エスケープされたコードポイントx
が、許容される最大値を超えているか、またはサロゲートペアの範囲(U+D800からU+DFFF)にある場合にエラーを報告します。
このコミットでは、0xd800
と0xe000
という16進数リテラルの表記が、それぞれ0xD800
と0xE000
に修正されました。これは機能的な変更ではなく、コードの可読性や一貫性を向上させるためのスタイル上の変更です。Go言語のコンパイラは、これらの16進数リテラルを同じ値として扱います。
関連リンク
- Go CL 6490095: https://golang.org/cl/6490095
参考にした情報源リンク
- バイトオーダーマーク - Wikipedia: https://ja.wikipedia.org/wiki/%E3%83%90%E3%82%A4%E3%83%88%E3%82%AA%E3%83%BC%E3%83%80%E3%83%BC%E3%83%9E%E3%83%BC%E3%82%AF
- Unicode - Wikipedia: https://ja.wikipedia.org/wiki/Unicode
- サロゲートペア - Wikipedia: https://ja.wikipedia.org/wiki/%E3%82%B5%E3%83%AD%E3%82%B2%E3%83%BC%E3%83%88%E3%83%9A%E3%82%A2
- The Go Programming Language Specification - String literals: https://go.dev/ref/spec#String_literals
- The Go Programming Language Specification - Character literals: https://go.dev/ref/spec#Character_literals
- Go source code for
go/scanner
package: https://pkg.go.dev/go/scanner