[インデックス 12415] ファイルの概要
このコミットは、Go言語のstrconv
パッケージに、文字が印字可能(printable)であるかを判定するためのテーブルベースのisPrint
関数とその生成ツールを追加するものです。具体的には、以下の3つのファイルが変更されています。
src/pkg/strconv/isprint.go
: 新規追加されたファイルで、印字可能なUnicodeコードポイントの範囲と例外を定義するテーブルが含まれています。このファイルは手動で編集されるものではなく、makeisprint.go
によって自動生成されます。src/pkg/strconv/makeisprint.go
: 新規追加されたファイルで、isprint.go
を生成するためのGoプログラムです。unicode
パッケージのIsPrint
関数を利用して、印字可能な文字の範囲を計算し、効率的なルックアップテーブルとして出力します。src/pkg/strconv/quote.go
: 既存のファイルで、isPrint
関数とそれに関連するバイナリサーチ関数(bsearch16
,bsearch32
)が追加されています。コミットメッセージには「Not used yet for simpler merge.」とありますが、このコミットでisPrint
関数がquote.go
内に定義され、将来的に利用される準備がされています。
コミット
commit e9d5a641d7c6c5d446c158f90249167133d5ccee
Author: Russ Cox <rsc@golang.org>
Date: Tue Mar 6 00:36:12 2012 -0500
strconv: add table-based isPrint
Not used yet for simpler merge.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5756048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e9d5a641d7c6c5d446c158f90249167133d5ccee
元コミット内容
strconv: add table-based isPrint
Not used yet for simpler merge.
変更の背景
このコミットの主な目的は、Go言語のstrconv
パッケージにおいて、Unicode文字が「印字可能」であるかを効率的に判定するメカニズムを導入することです。コミットメッセージにある「Not used yet for simpler merge.」という記述は、このisPrint
関数がすぐにstrconv
パッケージ内の既存の機能(例えば、文字列のリテラル化やクォート処理)で利用されるわけではないが、将来的な統合を容易にするために、まずテーブルと生成ロジックを導入したことを示唆しています。
Go言語では、文字列はUTF-8でエンコードされたバイト列として扱われ、個々の文字はrune
型(Unicodeコードポイントを表すint32
のエイリアス)で表現されます。文字列処理において、特定の文字が画面に表示されるべき「印字可能」な文字であるかどうかの判定は、セキュリティ上の理由(制御文字の除去など)や、表示の整形(引用符で囲む際のエスケープ処理など)のために重要となります。
以前のstrconv.IsPrint
の実装は、unicode.IsPrint
に依存していたと考えられます。unicode.IsPrint
は、Unicodeの文字プロパティに基づいて印字可能性を判定しますが、これは一般的に広範なUnicodeデータテーブルを内部的に参照するため、パフォーマンスが懸念される場合があります。特に、頻繁に呼び出される可能性のあるstrconv
パッケージのような低レベルな場所では、より高速な判定メカニズムが求められます。
このコミットは、unicode.IsPrint
のロジックを基にしつつも、strconv
パッケージ内で最適化されたテーブルルックアップ方式を導入することで、パフォーマンスの向上を図ることを意図しています。自動生成されるテーブルを使用することで、Unicodeの更新にも対応しやすくなります。
前提知識の解説
Go言語のrune
型とUnicode
Go言語において、文字列はUTF-8でエンコードされたバイト列として扱われます。個々の文字はrune
型で表現され、これは実質的にint32
のエイリアスであり、Unicodeのコードポイント(文字を一意に識別する番号)を表します。これにより、Goは多言語対応を容易にしています。
strconv
パッケージ
strconv
パッケージは、Go言語の標準ライブラリの一部であり、基本的なデータ型(整数、浮動小数点数、真偽値など)と文字列との間の変換機能を提供します。例えば、Atoi
(文字列を整数に変換)、Itoa
(整数を文字列に変換)、Quote
(文字列をGoのリテラル形式で引用符で囲む)などの関数があります。このパッケージは、文字列のパースやフォーマットにおいて非常に頻繁に利用されます。
unicode
パッケージ
unicode
パッケージは、Go言語の標準ライブラリの一部であり、Unicodeの文字プロパティに関する機能を提供します。これには、文字が数字であるか、文字であるか、空白であるか、あるいは印字可能であるかなどを判定する関数が含まれます。
unicode.IsPrint(r rune) bool
: この関数は、与えられたrune
がGoの定義する「印字可能」な文字であるかどうかを報告します。Goにおける印字可能な文字とは、文字、記号、数字、句読点、そしてASCIIスペース(U+0020)を指します。これには、改行、タブ、制御文字などの非印字文字は含まれません。
印字可能な文字 (Printable Characters)
「印字可能な文字」とは、通常、画面に表示されたり、紙に印刷されたりする際に視覚的な表現を持つ文字を指します。これには、アルファベット、数字、記号、句読点などが含まれます。対照的に、改行、タブ、バックスペース、NULL文字などの「制御文字」は、通常、視覚的な表現を持たず、特定の動作を制御するために使用されます。プログラミングにおいては、文字列処理や出力において、印字可能な文字と非印字可能な文字を区別することが重要になります。
ルックアップテーブル (Lookup Table)
ルックアップテーブルは、特定の入力値に対応する出力値を事前に計算して格納しておくデータ構造です。これにより、実行時に複雑な計算を繰り返す代わりに、テーブルから直接結果を取得できるため、処理速度を向上させることができます。文字のプロパティ判定においては、特定の文字コードが持つプロパティ(例: 印字可能かどうか)を、文字コードをインデックスとしてテーブルから参照することで、高速な判定が可能になります。
バイナリサーチ (Binary Search)
バイナリサーチ(二分探索)は、ソートされた配列から特定の要素を探し出すための効率的なアルゴリズムです。探索範囲を半分ずつに絞り込んでいくため、要素数N
に対してO(log N)
の計算量で動作します。このコミットでは、印字可能な文字の範囲や例外文字のリストがソートされた状態でテーブルに格納されており、特定のrune
がその範囲に含まれるか、または例外リストに含まれるかを判定するためにバイナリサーチが利用されています。
技術的詳細
このコミットで導入されたisPrint
の実装は、Unicodeのコードポイントを効率的に分類するために、事前に計算されたルックアップテーブルとバイナリサーチを組み合わせたものです。
テーブル構造
isprint.go
ファイルには、以下の4つのuint16
またはuint32
のスライス(配列)が定義されています。
isPrint16
:0x0000
から0xFFFF
までの範囲(つまりuint16
で表現できる範囲)の印字可能な文字の範囲を格納します。各範囲は[開始コードポイント, 終了コードポイント]
のペアで表現されます。例えば、0x0020, 0x007e
はASCIIスペースからチルダまでの範囲を示します。isNotPrint16
:0x0000
から0xFFFF
までの範囲で、isPrint16
の範囲に含まれるが、実際には印字可能ではない例外的な文字のコードポイントを格納します。isPrint32
:0x10000
からunicode.MaxRune
までの範囲(つまりuint32
で表現する必要がある範囲)の印字可能な文字の範囲を格納します。構造はisPrint16
と同様です。isNotPrint32
:0x10000
からunicode.MaxRune
までの範囲で、isPrint32
の範囲に含まれるが、実際には印字可能ではない例外的な文字のコードポイントを格納します。
これらのテーブルは、makeisprint.go
によって自動生成されます。makeisprint.go
は、unicode.IsPrint
関数を呼び出して各Unicodeコードポイントの印字可能性をチェックし、その結果に基づいて効率的な範囲と例外のリストを作成します。
isPrint
関数のロジック
quote.go
に追加されたisPrint(r rune) bool
関数は、以下のロジックで動作します。
-
rune
の範囲判定:- 入力された
rune
r
が0
以上かつ1<<16
(つまり65536
)未満の場合、そのrune
はuint16
で表現できる範囲にあると判断されます。この場合、isPrint16
とisNotPrint16
テーブルが使用されます。 - それ以外の場合、
rune
はuint32
で表現する必要がある範囲にあると判断され、isPrint32
とisNotPrint32
テーブルが使用されます。
- 入力された
-
印字可能範囲のチェック:
- 選択された
isPrint
テーブル(isPrint16
またはisPrint32
)に対して、入力rune
rr
(uint16
またはuint32
にキャストされたもの)をbsearch16
またはbsearch32
関数でバイナリサーチします。 bsearch
関数は、rr
以上の最初の要素のインデックスi
を返します。- このインデックス
i
と、その前後の要素(isPrint[i&^1]
とisPrint[i|1]
)を使って、rr
が印字可能範囲[isPrint[i&^1], isPrint[i|1]]
に含まれるかどうかをチェックします。i&^1
はi
を偶数に切り捨てるビット演算で、範囲の開始コードポイントを指します。i|1
はi
を奇数に切り上げるビット演算で、範囲の終了コードポイントを指します。
- もし
rr
がどの印字可能範囲にも含まれない場合、false
を返します。
- 選択された
-
例外リストのチェック:
rr
が印字可能範囲に含まれると判断された場合、次に選択されたisNotPrint
テーブル(isNotPrint16
またはisNotPrint32
)に対して、rr
をbsearch
関数でバイナリサーチします。bsearch
関数は、rr
以上の最初の要素のインデックスj
を返します。- もし
isNotPrint[j]
がrr
と等しい場合、そのrune
は例外リストに含まれるため、実際には印字可能ではないと判断され、false
を返します。 - 例外リストに含まれない場合、その
rune
は印字可能であると判断され、true
を返します。
bsearch16
/ bsearch32
関数
これらの関数は、ソートされたuint16
またはuint32
のスライスa
の中から、与えられた値x
以上の最小の要素のインデックスを返すバイナリサーチの実装です。もしそのような要素が存在しない場合、スライスの長さ(len(a)
)を返します。
makeisprint.go
の役割
makeisprint.go
は、isprint.go
を生成するためのツールです。
scan(min, max rune)
関数は、指定されたmin
からmax
までのrune
の範囲を走査し、unicode.IsPrint
の結果に基づいて印字可能な文字の範囲(rang
)と例外文字(except
)を収集します。to16(x []uint32)
関数は、uint32
のスライスをuint16
のスライスに変換します。main
関数では、0
から0xFFFF
までの範囲と、0x10000
からunicode.MaxRune
までの範囲でscan
を実行し、それぞれisPrint16
,isNotPrint16
,isPrint32
,isNotPrint32
の各テーブルを生成し、isprint.go
の形式で標準出力に出力します。これにより、isprint.go
は常に最新のUnicodeプロパティに基づいて生成されることが保証されます。
このテーブルベースのアプローチは、unicode
パッケージが提供する汎用的なIsPrint
関数よりも、特定のstrconv
のユースケースにおいて高速な判定を可能にすることを目的としています。
コアとなるコードの変更箇所
src/pkg/strconv/isprint.go
(新規追加)
このファイルは、makeisprint.go
によって自動生成されるため、手動での編集は推奨されません。
内容は、isPrint16
, isNotPrint16
, isPrint32
, isNotPrint32
という4つのuint16
またはuint32
のスライス定義です。これらは、印字可能な文字の範囲と、その範囲内にあるが印字可能ではない例外文字のリストをそれぞれ格納しています。
例:
// DO NOT EDIT. GENERATED BY
// go run makeisprint.go >x && mv x isprint.go
package strconv
var isPrint16 = []uint16{
0x0020, 0x007e, // ASCII Space to Tilde
// ... 多数の範囲定義 ...
}
var isNotPrint16 = []uint16{
0x00ad, // Soft Hyphen
// ... 多数の例外文字定義 ...
}
var isPrint32 = []uint32{
0x000020, 0x00007e,
// ... 多数の範囲定義 ...
}
var isNotPrint32 = []uint32{
0x1000c,
// ... 多数の例外文字定義 ...
}
src/pkg/strconv/makeisprint.go
(新規追加)
このファイルは、isprint.go
を生成するためのGoプログラムです。
unicode
パッケージをインポートし、unicode.IsPrint
関数を使用して印字可能な文字の範囲を特定します。
bsearch16
, bsearch32
関数は、生成されたテーブルのテストのために一時的に含まれていますが、最終的なisprint.go
には含まれません。
scan
関数がUnicodeの全範囲を走査し、印字可能な文字の範囲と例外を収集します。
main
関数がこれらの情報を整形し、isprint.go
の形式で標準出力に出力します。
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// makeisprint generates the tables for strconv's compact isPrint.
package main
import (
"fmt"
"unicode"
)
// ... bsearch16, bsearch32, isPrint (テスト用) 関数定義 ...
func scan(min, max rune) (rang, except []uint32) {
// minからmaxまでのruneを走査し、unicode.IsPrintの結果に基づいて範囲と例外を収集
}
func to16(x []uint32) []uint16 {
// uint32スライスをuint16スライスに変換
}
func main() {
// isPrint16, isNotPrint16, isPrint32, isNotPrint32 の各テーブルを生成
// 生成されたテーブルをGoのコードとして標準出力に出力
}
src/pkg/strconv/quote.go
(変更)
このファイルには、isPrint
関数と、その内部で利用されるbsearch16
およびbsearch32
関数が追加されています。これらの関数は、isprint.go
で定義されたテーブルを参照して印字可能性を判定します。
// ... 既存のコード ...
// bsearch16 returns the smallest i such that a[i] >= x.
// If there is no such i, bsearch16 returns len(a).
func bsearch16(a []uint16, x uint16) int {
i, j := 0, len(a)
for i < j {
h := i + (j-i)/2
if a[h] < x {
i = h + 1
} else {
j = h
}
}
return i
}
// bsearch32 returns the smallest i such that a[i] >= x.
// If there is no such i, bsearch32 returns len(a).
func bsearch32(a []uint32, x uint32) int {
i, j := 0, len(a)
for i < j {
h := i + (j-i)/2
if a[h] < x {
i = h + 1
} else {
j = h
}
}
return i
}
func isPrint(r rune) bool {
// Same algorithm, either on uint16 or uint32 value.
// First, find first i such that isPrint[i] >= x.
// This is the index of either the start or end of a pair that might span x.
// The start is even (isPrint[i&^1]) and the end is odd (isPrint[i|1]).
// If we find x in a range, make sure x is not in isNotPrint list.
if 0 <= r && r < 1<<16 {
rr, isPrint, isNotPrint := uint16(r), isPrint16, isNotPrint16
i := bsearch16(isPrint, rr)
if i >= len(isPrint) || rr < isPrint[i&^1] || isPrint[i|1] < rr {
return false
}
j := bsearch16(isNotPrint, rr)
return j >= len(isNotPrint) || isNotPrint[j] != rr
}
rr, isPrint, isNotPrint := uint32(r), isPrint32, isNotPrint32
i := bsearch32(isPrint, rr)
if i >= len(isPrint) || rr < isPrint[i&^1] || isPrint[i|1] < rr {
return false
}
j := bsearch32(isNotPrint, rr)
return j >= len(isNotPrint) || isNotPrint[j] != rr
}
コアとなるコードの解説
このコミットの核心は、strconv
パッケージにおけるisPrint
関数の実装を、動的な判定から静的なルックアップテーブルに基づく判定へと移行させる点にあります。
isprint.go
このファイルは、makeisprint.go
によって生成されるデータファイルです。Goのソースコードとして、印字可能な文字の範囲と例外文字のリストをuint16
とuint32
の配列として定義しています。これらの配列は、Unicodeの全コードポイントを網羅的にチェックするのではなく、印字可能な文字が連続する「範囲」と、その範囲内にあるが印字可能ではない「例外」を効率的に表現しています。これにより、メモリ使用量を抑えつつ、高速なルックアップを可能にしています。
makeisprint.go
このプログラムは、isprint.go
を生成する「ジェネレータ」です。unicode
パッケージのIsPrint
関数を呼び出すことで、Go言語が定義する印字可能性の基準に従って、各Unicodeコードポイントの印字可能性を判定します。そして、その結果を基に、isprint.go
で使われるisPrint16
, isNotPrint16
, isPrint32
, isNotPrint32
の各テーブルを構築します。この自動生成プロセスにより、Unicodeの新しいバージョンがリリースされた際にも、makeisprint.go
を再実行するだけでisprint.go
を簡単に更新でき、メンテナンス性が向上します。
quote.go
内のisPrint
関数とbsearch
関数
quote.go
に追加されたisPrint
関数は、実際に文字の印字可能性を判定するロジックを実装しています。この関数は、入力されたrune
がuint16
の範囲かuint32
の範囲かによって、使用するテーブル(isPrint16
/isNotPrint16
またはisPrint32
/isNotPrint32
)を切り替えます。
判定ロジックは以下のステップで行われます。
- 範囲の特定:
bsearch
関数(bsearch16
またはbsearch32
)を使用して、入力rune
が印字可能な文字の範囲を定義するテーブル(isPrint16
またはisPrint32
)内のどの範囲に属するかを効率的に特定します。bsearch
はソートされた配列に対して二分探索を行うため、非常に高速です。 - 範囲内チェック:
bsearch
で得られたインデックスと、そのインデックスが指す範囲の開始・終了コードポイントを使って、入力rune
がその範囲内に実際に含まれるかを確認します。 - 例外チェック: もし入力
rune
が印字可能範囲に含まれる場合、次にisNotPrint
テーブル(isNotPrint16
またはisNotPrint32
)に対して再度bsearch
を実行し、入力rune
が印字可能ではない例外文字のリストに含まれていないかを確認します。
この二段階のチェックとテーブルベースのルックアップ、そしてバイナリサーチの組み合わせにより、isPrint
関数は非常に効率的に動作し、strconv
パッケージのパフォーマンス向上に貢献します。コミットメッセージにある「Not used yet for simpler merge.」は、このisPrint
関数がまだquote.go
内の他の関数から直接呼び出されていないことを示していますが、将来的にstrconv.Quote
などの関数で、文字列を引用符で囲む際にどの文字をエスケープすべきかを判断するために利用されることが想定されます。
関連リンク
- Go CL 5756048: https://golang.org/cl/5756048
参考にした情報源リンク
- Go言語
strconv
パッケージ: https://pkg.go.dev/strconv - Go言語
unicode
パッケージ: https://pkg.go.dev/unicode unicode.IsPrint
の説明 (GeeksforGeeks): https://www.geeksforgeeks.org/go-unicode-isprint-function/unicode.IsPrint
の説明 (Go.dev): https://pkg.go.dev/unicode#IsPrint- Go言語における
strconv.IsPrint
の実装に関する情報 (GitHub): https://github.com/golang/go/blob/master/src/strconv/isprint.go - Go言語における
makeisprint.go
の実装に関する情報 (GitHub): https://github.com/golang/go/blob/master/src/strconv/makeisprint.go - Go言語における
quote.go
の実装に関する情報 (GitHub): https://github.com/golang/go/blob/master/src/strconv/quote.go