[インデックス 10097] ファイルの概要
このコミットは、Go言語の標準ライブラリであるregexp
パッケージにおいて、正規表現エンジンが文字を扱う内部表現をint
型からrune
型へ変更するものです。これにより、Unicode文字の正確な処理が保証され、正規表現の機能がより堅牢になります。特に、正規表現の構文ツリーの公開APIにも変更が加えられています。
コミット
- Author: Russ Cox rsc@golang.org
- Date: Tue Oct 25 22:20:57 2011 -0700
- Original Commit Message:
regexp: use rune Public API of syntax tree changes. R=golang-dev, r, gri CC=golang-dev https://golang.org/cl/5302046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3e52dadfd7b4c43c1d630d510eeb1b289d2ab422
元コミット内容
正規表現エンジンにおいて、文字の内部表現をrune
型に移行する。
これにより、構文ツリーの公開APIが変更される。
変更の背景
Go言語は設計当初からUnicodeを強く意識しており、文字列はUTF-8でエンコードされたバイト列として扱われます。しかし、個々の文字(Unicodeコードポイント)を扱う際には、バイト列からデコードする必要があります。初期のGoの正規表現パッケージでは、文字をint
型で表現している箇所がありました。これはASCII文字には問題ありませんが、Unicodeの多バイト文字を正確に扱うためには不十分でした。
int
型は通常、システム依存のサイズ(32ビットまたは64ビット)を持ち、文字コードポイントを直接表現するには曖昧さや潜在的な問題がありました。特に、UnicodeのコードポイントはU+0000からU+10FFFFまでの範囲に及び、int
型で表現すると、その値が文字コードポイントなのか、それとも他の数値データなのかが区別しにくい場合があります。
このコミットの背景には、Goの正規表現エンジンがUnicodeの全範囲の文字を正しく処理できるようにするための改善があります。rune
型はGoにおいてUnicodeコードポイントを表現するための専用の型(int32
のエイリアス)であり、これを使用することで、文字の扱いがより明確かつ安全になります。これにより、正規表現が多言語のテキストに対して期待通りに動作することが保証されます。また、「Public API of syntax tree changes」という記述から、この変更が正規表現の内部構造だけでなく、それを操作する外部インターフェースにも影響を与えることが示唆されています。
前提知識の解説
Go言語におけるrune
型とUnicode、UTF-8
Go言語では、文字列は不変のバイトスライスとして扱われ、通常はUTF-8でエンコードされています。
string
: UTF-8でエンコードされたバイト列のシーケンスです。個々の文字に直接アクセスすることはできません。byte
: 8ビットの符号なし整数(uint8
のエイリアス)で、ASCII文字やバイトデータを表現します。rune
: Unicodeコードポイントを表す型で、int32
のエイリアスです。Goでは、for range
ループで文字列をイテレートすると、各要素はrune
型として取得され、UTF-8デコードが自動的に行われます。これにより、多バイト文字も正しく1つのrune
として扱えます。
UnicodeとUTF-8:
- Unicode: 世界中の文字を統一的に扱うための文字コードの国際標準です。各文字には一意の「コードポイント」(数値)が割り当てられています。
- UTF-8: Unicodeコードポイントをバイト列にエンコードするための可変長エンコーディング方式です。ASCII文字は1バイトで表現され、それ以外の文字は2バイト以上で表現されます。これにより、ASCIIとの互換性を保ちつつ、効率的にUnicode文字を表現できます。
正規表現エンジンが文字を扱う際、int
型を使用すると、それが単なる数値なのか、それともUnicodeコードポイントなのかが不明瞭になる可能性があります。rune
型を明示的に使用することで、コードの意図が明確になり、Unicode文字の正確な処理が保証されます。
正規表現の内部構造と文字集合
正規表現エンジンは、入力された正規表現パターンを解析し、内部的に「構文ツリー」(Syntax Tree)や「有限オートマトン」(Finite Automaton)などのデータ構造に変換して処理を行います。このデータ構造内で文字を表現する際、どの型を使用するかが重要になります。
文字集合(Character Class)は、正規表現において特定の文字のグループ(例: [a-z]
, \d
, \s
)を表現するために使われます。これらの文字集合がUnicodeの全範囲を正しくカバーするためには、内部でUnicodeコードポイントを正確に扱える型が必要です。
技術的詳細
このコミットの主要な変更は、Goのregexp
パッケージ内で文字(Unicodeコードポイント)を表現するために使用されていたint
型を、Goの組み込み型であるrune
型に置き換えることです。この変更は、正規表現エンジンの複数の層にわたって影響を及ぼします。
regexp.Regexp
構造体: 正規表現オブジェクトの内部構造において、prefixRune
やRune
フィールドがint
からrune
に変更されます。これは、正規表現が持つリテラルプレフィックスや文字クラスの定義が、より正確にUnicodeコードポイントとして扱われることを意味します。input
インターフェース: 正規表現エンジンが入力テキストを読み込むためのインターフェースであるinput
のstep
メソッドの戻り値がint
からrune
に変更されます。これにより、入力ストリームから読み込まれる各文字がrune
として直接扱われるようになります。syntax
パッケージ: 正規表現の構文解析とコンパイルを担当するsyntax
パッケージ内で、文字クラス([]int
)や個々の文字を扱う多くの関数引数や変数(例:tmpClass
,r
,lo
,hi
)が[]rune
やrune
に変更されます。これは、正規表現の構文ツリーが文字情報をrune
として保持し、操作することを意味します。syntax/compile.go
: 正規表現をバイトコードにコンパイルする際に、文字クラスの定義が[]rune
になります。syntax/parse.go
: 正規表現文字列を解析して構文ツリーを構築する際に、文字や文字クラスをrune
として処理します。特に、エスケープシーケンスの解析や文字クラスの結合・否定などのロジックがrune
ベースになります。syntax/perl_groups.go
: Perl互換の文字グループ(例:\d
,\s
,\w
)の定義が[]rune
になります。syntax/prog.go
: コンパイルされた正規表現プログラムの命令(Inst
)が持つ文字情報(Rune
フィールド)や、文字の比較を行う関数(MatchRune
,IsWordChar
,EmptyOpContext
など)の引数がrune
になります。
make_perl_groups.pl
スクリプト: このスクリプトはPerl互換の文字グループを生成するために使用されますが、生成されるGoコードの文字クラス定義が[]int
から[]rune
になるように変更されます。
この変更により、正規表現エンジンはUnicodeのサロゲートペアや結合文字シーケンスなど、複雑なUnicodeの特性を持つ文字も正しく認識し、マッチングできるようになります。例えば、絵文字や特定の言語の文字など、int
型では単一の文字として扱えなかったものが、rune
型によって正しく処理されるようになります。
コアとなるコードの変更箇所
このコミットでは、int
型で文字を扱っていた箇所がrune
型に変更されています。以下に主要な変更箇所を抜粋し、その意図を解説します。
src/pkg/regexp/exec.go
--- a/src/pkg/regexp/exec.go
+++ b/src/pkg/regexp/exec.go
@@ -90,15 +90,15 @@ func (m *machine) match(i input, pos int) bool {
m.matchcap[i] = -1
}
runq, nextq := &m.q0, &m.q1
- rune, rune1 := endOfText, endOfText
+ r, r1 := endOfText, endOfText
width, width1 := 0, 0
- rune, width = i.step(pos)
- if rune != endOfText {
- rune1, width1 = i.step(pos + width)
+ r, width = i.step(pos)
+ if r != endOfText {
+ r1, width1 = i.step(pos + width)
}
var flag syntax.EmptyOp
if pos == 0 {
- flag = syntax.EmptyOpContext(-1, rune)
+ flag = syntax.EmptyOpContext(-1, r)
} else {
flag = i.context(pos)
}
@@ -112,15 +112,15 @@ func (m *machine) match(i input, pos int) bool {
// Have match; finished exploring alternatives.
break
}
- if len(m.re.prefix) > 0 && rune1 != m.re.prefixRune && i.canCheckPrefix() {
+ if len(m.re.prefix) > 0 && r1 != m.re.prefixRune && i.canCheckPrefix() {
// Match requires literal prefix; fast search for it.
advance := i.index(m.re, pos)
if advance < 0 {
break
}
pos += advance
- rune, width = i.step(pos)
- rune1, width1 = i.step(pos + width)
+ r, width = i.step(pos)
+ r1, width1 = i.step(pos + width)
}
}
if !m.matched {
@@ -129,8 +129,8 @@ func (m *machine) match(i input, pos int) bool {
}
m.add(runq, uint32(m.p.Start), pos, m.matchcap, flag, nil)
}
- flag = syntax.EmptyOpContext(rune, rune1)
- m.step(runq, nextq, pos, pos+width, rune, flag)
+ flag = syntax.EmptyOpContext(r, r1)
+ m.step(runq, nextq, pos, pos+width, r, flag)
if width == 0 {
break
}
@@ -140,9 +140,9 @@ func (m *machine) match(i input, pos int) bool {
break
}
pos += width
- rune, width = rune1, width1
- if rune != endOfText {
- rune1, width1 = i.step(pos + width)
+ r, width = r1, width1
+ if r != endOfText {
+ r1, width1 = i.step(pos + width)
}
runq, nextq = nextq, runq
}
@@ -166,7 +166,7 @@ func (m *machine) clear(q *queue) {
// The step processes the rune c (which may be endOfText),
// which starts at position pos and ends at nextPos.
// nextCond gives the setting for the empty-width flags after c.
-func (m *machine) step(runq, nextq *queue, pos, nextPos, c int, nextCond syntax.EmptyOp) {
+func (m *machine) step(runq, nextq *queue, pos, nextPos int, c rune, nextCond syntax.EmptyOp) {
longest := m.re.longest
for j := 0; j < len(runq.dense); j++ {
d := &runq.dense[j]
src/pkg/regexp/regexp.go
--- a/src/pkg/regexp/regexp.go
+++ b/src/pkg/regexp/regexp.go
@@ -83,7 +83,7 @@ type Regexp struct {
prefix string // required prefix in unanchored matches
prefixBytes []byte // prefix, as a []byte
prefixComplete bool // prefix is the entire regexp
- prefixRune int // first rune in prefix
+ prefixRune rune // first rune in prefix
cond syntax.EmptyOp // empty-width conditions required at start of match
numSubexp int
longest bool
@@ -224,13 +224,13 @@ func (re *Regexp) NumSubexp() int {
return re.numSubexp
}
-const endOfText = -1
+const endOfText rune = -1
// input abstracts different representations of the input text. It provides
// one-character lookahead.
type input interface {
- step(pos int) (rune int, width int) // advance one rune
- canCheckPrefix() bool // can we look ahead without losing info?
+ step(pos int) (r rune, width int) // advance one rune
+ canCheckPrefix() bool // can we look ahead without losing info?
hasPrefix(re *Regexp) bool
index(re *Regexp, pos int) int
context(pos int) syntax.EmptyOp
@@ -245,11 +245,11 @@ func newInputString(str string) *inputString {
return &inputString{str: str}
}
-func (i *inputString) step(pos int) (int, int) {
+func (i *inputString) step(pos int) (rune, int) {
if pos < len(i.str) {
c := i.str[pos]
if c < utf8.RuneSelf {
- return int(c), 1
+ return rune(c), 1
}
return utf8.DecodeRuneInString(i.str[pos:])
}
@@ -269,7 +269,7 @@ func (i *inputString) index(re *Regexp, pos int) int {
}
func (i *inputString) context(pos int) syntax.EmptyOp {
- r1, r2 := -1, -1
+ r1, r2 := endOfText, endOfText
if pos > 0 && pos <= len(i.str) {
r1, _ = utf8.DecodeLastRuneInString(i.str[:pos])
}
@@ -288,11 +288,11 @@ func newInputBytes(str []byte) *inputBytes {
return &inputBytes{str: str}
}
-func (i *inputBytes) step(pos int) (int, int) {
+func (i *inputBytes) step(pos int) (rune, int) {
if pos < len(i.str) {
c := i.str[pos]
if c < utf8.RuneSelf {
- return int(c), 1
+ return rune(c), 1
}
return utf8.DecodeRune(i.str[pos:])
}
@@ -312,7 +312,7 @@ func (i *inputBytes) index(re *Regexp, pos int) int {
}
func (i *inputBytes) context(pos int) syntax.EmptyOp {
- r1, r2 := -1, -1
+ r1, r2 := endOfText, endOfText
if pos > 0 && pos <= len(i.str) {
r1, _ = utf8.DecodeLastRune(i.str[:pos])
}
@@ -333,7 +333,7 @@ func newInputReader(r io.RuneReader) *inputReader {
return &inputReader{r: r}
}
-func (i *inputReader) step(pos int) (int, int) {
+func (i *inputReader) step(pos int) (rune, int) {
if !i.atEOT && pos != i.pos {
return endOfText, 0
src/pkg/regexp/syntax/parse.go
--- a/src/pkg/regexp/syntax/parse.go
+++ b/src/pkg/regexp/syntax/parse.go
@@ -82,7 +82,7 @@ type parser struct {
free *Regexp
numCap int // number of capturing groups seen
wholeRegexp string
- tmpClass []int // temporary char class work space
+ tmpClass []rune // temporary char class work space
}
func (p *parser) newRegexp(op Op) *Regexp {
@@ -149,7 +149,7 @@ func (p *parser) push(re *Regexp) *Regexp {
// If r >= 0 and there's a node left over, maybeConcat uses it
// to push r with the given flags.
// maybeConcat reports whether r was pushed.
-func (p *parser) maybeConcat(r int, flags Flags) bool {
+func (p *parser) maybeConcat(r rune, flags Flags) bool {
n := len(p.stack)
if n < 2 {
return false
@@ -178,7 +178,7 @@ func (p *parser) maybeConcat(r int, flags Flags) bool {
}
// newLiteral returns a new OpLiteral Regexp with the given flags
-func (p *parser) newLiteral(r int, flags Flags) *Regexp {
+func (p *parser) newLiteral(r rune, flags Flags) *Regexp {
re := p.newRegexp(OpLiteral)
re.Flags = flags
if flags&FoldCase != 0 {
@@ -190,7 +190,7 @@ func (p *parser) newLiteral(r int, flags Flags) *Regexp {
}
// minFoldRune returns the minimum rune fold-equivalent to r.
-func minFoldRune(r int) int {
+func minFoldRune(r rune) rune {
if r < minFold || r > maxFold {
return r
}
@@ -206,7 +206,7 @@ func minFoldRune(r int) int {
// literal pushes a literal regexp for the rune r on the stack
// and returns that regexp.
-func (p *parser) literal(r int) {
+func (p *parser) literal(r rune) {
p.push(p.newLiteral(r, p.flags))
}
@@ -369,7 +369,7 @@ func (p *parser) factor(sub []*Regexp, flags Flags) []*Regexp {
}
// Round 1: Factor out common literal prefixes.
- var str []int
+ var str []rune
var strflags Flags
start := 0
out := sub[:0]
@@ -380,7 +380,7 @@ func (p *parser) factor(sub []*Regexp, flags Flags) []*Regexp {
//
// Invariant: sub[start:i] consists of regexps that all begin
// with str as modified by strflags.
- var istr []int
+ var istr []rune
var iflags Flags
if i < len(sub) {
istr, iflags = p.leadingString(sub[i])
@@ -543,7 +543,7 @@ func (p *parser) factor(sub []*Regexp, flags Flags) []*Regexp {
// leadingString returns the leading literal string that re begins with.
// The string refers to storage in re or its children.
-func (p *parser) leadingString(re *Regexp) ([]int, Flags) {
+func (p *parser) leadingString(re *Regexp) ([]rune, Flags) {
if re.Op == OpConcat && len(re.Sub) > 0 {
re = re.Sub[0]
}
@@ -639,7 +639,7 @@ func literalRegexp(s string, flags Flags) *Regexp {
for _, c := range s {
if len(re.Rune) >= cap(re.Rune) {
// string is too long to fit in Rune0. let Go handle it
- re.Rune = []int(s)
+ re.Rune = []rune(s)
break
}
re.Rune = append(re.Rune, c)
@@ -662,7 +662,7 @@ func Parse(s string, flags Flags) (*Regexp, os.Error) {
var (
p parser
err os.Error
- c int
+ c rune
op Op
lastRepeat string
min, max int
@@ -935,7 +935,7 @@ func (p *parser) parsePerlFlags(s string) (rest string, err os.Error) {
}
// Non-capturing group. Might also twiddle Perl flags.
- var c int
+ var c rune
t = t[2:] // skip (?
flags := p.flags
sign := +1
@@ -1049,7 +1049,7 @@ func isCharClass(re *Regexp) bool {\n }\n \n // does re match r?\n-func matchRune(re *Regexp, r int) bool {\n+func matchRune(re *Regexp, r rune) bool {\n switch re.Op {\n case OpLiteral:\n return len(re.Rune) == 1 && re.Rune[0] == r\n@@ -1186,7 +1186,7 @@ func (p *parser) parseRightParen() os.Error {\n \n // parseEscape parses an escape sequence at the beginning of s\n // and returns the rune.\n-func (p *parser) parseEscape(s string) (r int, rest string, err os.Error) {\n+func (p *parser) parseEscape(s string) (r rune, rest string, err os.Error) {\n t := s[1:]\n if t == "" {\n return 0, "", &Error{ErrTrailingBackslash, ""}\n@@ -1221,7 +1221,7 @@ Switch:\n if t == "" || t[0] < '0' || t[0] > '7' {\n break\n }\n- r = r*8 + int(t[0]) - '0'\n+ r = r*8 + rune(t[0]) - '0'\n t = t[1:]\n }\n return r, t, nil\n@@ -1302,7 +1302,7 @@ Switch:\n
// parseClassChar parses a character class character at the beginning of s
// and returns it.
-func (p *parser) parseClassChar(s, wholeClass string) (r int, rest string, err os.Error) {\n+func (p *parser) parseClassChar(s, wholeClass string) (r rune, rest string, err os.Error) {\n if s == "" {\n return 0, "", &Error{Code: ErrMissingBracket, Expr: wholeClass}\n }\n@@ -1318,13 +1318,13 @@ func (p *parser) parseClassChar(s, wholeClass string) (r int, rest string, err o\n
type charGroup struct {
sign int
- class []int
+ class []rune
}
// parsePerlClassEscape parses a leading Perl character class escape like \d
// from the beginning of s. If one is present, it appends the characters to r
// and returns the new slice r and the remainder of the string.
-func (p *parser) parsePerlClassEscape(s string, r []int) (out []int, rest string) {\n+func (p *parser) parsePerlClassEscape(s string, r []rune) (out []rune, rest string) {\n if p.flags&PerlX == 0 || len(s) < 2 || s[0] != '\\' || s[1] != 'p' && s[1] != 'P' {\n return\n }\n@@ -1338,7 +1338,7 @@ func (p *parser) parsePerlClassEscape(s string, r []int) (out []int, rest string\n // parseNamedClass parses a leading POSIX named character class like [:alnum:]
// from the beginning of s. If one is present, it appends the characters to r
// and returns the new slice r and the remainder of the string.
-func (p *parser) parseNamedClass(s string, r []int) (out []int, rest string, err os.Error) {\n+func (p *parser) parseNamedClass(s string, r []rune) (out []rune, rest string, err os.Error) {\n if len(s) < 2 || s[0] != '[' || s[1] != ':' {\n return\n }\n@@ -1356,7 +1356,7 @@ func (p *parser) parseNamedClass(s string, r []int) (out []int, rest string, err\n return p.appendGroup(r, g), s, nil
}
-func (p *parser) appendGroup(r []int, g charGroup) []int {\n+func (p *parser) appendGroup(r []rune, g charGroup) []rune {\n if p.flags&FoldCase == 0 {\n if g.sign < 0 {\n r = appendNegatedClass(r, g.class)\n@@ -1401,7 +1401,7 @@ func unicodeTable(name string) (*unicode.RangeTable, *unicode.RangeTable) {\n // parseUnicodeClass parses a leading Unicode character class like \p{Han}
// from the beginning of s. If one is present, it appends the characters to r
// and returns the new slice r and the remainder of the string.
-func (p *parser) parseUnicodeClass(s string, r []int) (out []int, rest string, err os.Error) {\n+func (p *parser) parseUnicodeClass(s string, r []rune) (out []rune, rest string, err os.Error) {\n if p.flags&UnicodeGroups == 0 || len(s) < 2 || s[0] != '\\' || s[1] != 'p' && s[1] != 'P' {\n return\n }\n@@ -1533,7 +1533,7 @@ func (p *parser) parseClass(s string) (rest string, err os.Error) {\n
// Single character or simple range.
rng := t
- var lo, hi int
+ var lo, hi rune
if lo, t, err = p.parseClassChar(t, s); err != nil {\n return "", err
}\n@@ -1570,7 +1570,7 @@ func (p *parser) parseClass(s string) (rest string, err os.Error) {\n
// cleanClass sorts the ranges (pairs of elements of r),
// merges them, and eliminates duplicates.
-func cleanClass(rp *[]int) []int {\n+func cleanClass(rp *[]rune) []rune {\n
// Sort by lo increasing, hi decreasing to break ties.
sort.Sort(ranges{rp})\n@@ -1601,7 +1601,7 @@ func cleanClass(rp *[]int) []int {\n }\n
// appendLiteral returns the result of appending the literal x to the class r.
-func appendLiteral(r []int, x int, flags Flags) []int {\n+func appendLiteral(r []rune, x rune, flags Flags) []rune {\n if flags&FoldCase != 0 {\n return appendFoldedRange(r, x, x)\n }\n@@ -1609,7 +1609,7 @@ func appendLiteral(r []int, x int, flags Flags) []int {\n }\n
// appendRange returns the result of appending the range lo-hi to the class r.
-func appendRange(r []int, lo, hi int) []int {\n+func appendRange(r []rune, lo, hi rune) []rune {\n // Expand last range or next to last range if it overlaps or abuts.\n // Checking two ranges helps when appending case-folded\n // alphabets, so that one range can be expanding A-Z and the\n@@ -1642,7 +1642,7 @@ const (\n
// appendFoldedRange returns the result of appending the range lo-hi
// and its case folding-equivalent runes to the class r.
-func appendFoldedRange(r []int, lo, hi int) []int {\n+func appendFoldedRange(r []rune, lo, hi rune) []rune {\n // Optimizations.\n if lo <= minFold && hi >= maxFold {\n // Range is full: folding can't add more.\n@@ -1677,7 +1677,7 @@ func appendFoldedRange(r []int, lo, hi int) []int {\n
// appendClass returns the result of appending the class x to the class r.
// It assume x is clean.
-func appendClass(r []int, x []int) []int {\n+func appendClass(r []rune, x []rune) []rune {\n for i := 0; i < len(x); i += 2 {\n r = appendRange(r, x[i], x[i+1])\n }\n@@ -1685,7 +1685,7 @@ func appendClass(r []int, x []int) []int {\n }\n
// appendFolded returns the result of appending the case folding of the class x to the class r.
-func appendFoldedClass(r []int, x []int) []int {\n+func appendFoldedClass(r []rune, x []rune) []rune {\n for i := 0; i < len(x); i += 2 {\n r = appendFoldedRange(r, x[i], x[i+1])\n }\n@@ -1694,8 +1694,8 @@ func appendFoldedClass(r []int, x []int) []int {\n
// appendNegatedClass returns the result of appending the negation of the class x to the class r.
// It assumes x is clean.
-func appendNegatedClass(r []int, x []int) []int {\n- nextLo := 0\n+func appendNegatedClass(r []rune, x []rune) []rune {\n+ nextLo := rune('\u0000')\n for i := 0; i < len(x); i += 2 {\n lo, hi := x[i], x[i+1]\n if nextLo <= lo-1 {\n@@ -1710,9 +1710,9 @@ func appendNegatedClass(r []int, x []int) []int {\n }\n
// appendTable returns the result of appending x to the class r.
-func appendTable(r []int, x *unicode.RangeTable) []int {\n+func appendTable(r []rune, x *unicode.RangeTable) []rune {\n for _, xr := range x.R16 {\n- lo, hi, stride := int(xr.Lo), int(xr.Hi), int(xr.Stride)\n+ lo, hi, stride := rune(xr.Lo), rune(xr.Hi), rune(xr.Stride)\n if stride == 1 {\n r = appendRange(r, lo, hi)\n continue\n@@ -1722,7 +1722,7 @@ func appendTable(r []int, x *unicode.RangeTable) []int {\n }\n }\n for _, xr := range x.R32 {\n- lo, hi, stride := int(xr.Lo), int(xr.Hi), int(xr.Stride)\n+ lo, hi, stride := rune(xr.Lo), rune(xr.Hi), rune(xr.Stride)\n if stride == 1 {\n r = appendRange(r, lo, hi)\n continue\n@@ -1735,10 +1735,10 @@ func appendTable(r []int, x *unicode.RangeTable) []int {\n }\n
// appendNegatedTable returns the result of appending the negation of x to the class r.
-func appendNegatedTable(r []int, x *unicode.RangeTable) []int {\n- nextLo := 0 // lo end of next class to add\n+func appendNegatedTable(r []rune, x *unicode.RangeTable) []rune {\n+ nextLo := rune('\u0000') // lo end of next class to add\n for _, xr := range x.R16 {\n- lo, hi, stride := int(xr.Lo), int(xr.Hi), int(xr.Stride)\n+ lo, hi, stride := rune(xr.Lo), rune(xr.Hi), rune(xr.Stride)\n if stride == 1 {\n if nextLo <= lo-1 {\n r = appendRange(r, nextLo, lo-1)\n@@ -1754,7 +1754,7 @@ func appendNegatedTable(r []int, x *unicode.RangeTable) []int {\n }\n }\n for _, xr := range x.R32 {\n- lo, hi, stride := int(xr.Lo), int(xr.Hi), int(xr.Stride)\n+ lo, hi, stride := rune(xr.Lo), rune(xr.Hi), rune(xr.Stride)\n if stride == 1 {\n if nextLo <= lo-1 {\n r = appendRange(r, nextLo, lo-1)\n@@ -1777,9 +1777,9 @@ func appendNegatedTable(r []int, x *unicode.RangeTable) []int {\n
// negateClass overwrites r and returns r's negation.
// It assumes the class r is already clean.
-func negateClass(r []int) []int {\n- nextLo := 0 // lo end of next class to add\n- w := 0 // write index\n+func negateClass(r []rune) []rune {\n+ nextLo := rune('\u0000') // lo end of next class to add\n+ w := 0 // write index\n for i := 0; i < len(r); i += 2 {\n lo, hi := r[i], r[i+1]\n if nextLo <= lo-1 {\n@@ -1801,9 +1801,9 @@ func negateClass(r []int) []int {\n // ranges implements sort.Interface on a []rune.
// The choice of receiver type definition is strange
// but avoids an allocation since we already have
-// a *[]int.
+// a *[]rune.
type ranges struct {
- p *[]int
+ p *[]rune
}
func (ra ranges) Less(i, j int) bool {
@@ -1835,15 +1835,15 @@ func checkUTF8(s string) os.Error {\n return nil
}\n
-func nextRune(s string) (c int, t string, err os.Error) {\n+func nextRune(s string) (c rune, t string, err os.Error) {\n c, size := utf8.DecodeRuneInString(s)\n if c == utf8.RuneError && size == 1 {\n return 0, "", &Error{Code: ErrInvalidUTF8, Expr: s}\n }\n return c, s[size:], nil
}\n
-func isalnum(c int) bool {\n+func isalnum(c rune) bool {\n return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'\n }\n
-func unhex(c int) int {\n+func unhex(c rune) rune {\n if '0' <= c && c <= '9' {\n return c - '0'\n }\n```
## コアとなるコードの解説
上記の変更箇所は、Goの正規表現エンジンが文字を扱う際の基盤となる部分です。
1. **変数名の変更 (`rune` -> `r`, `rune1` -> `r1`)**:
* `src/pkg/regexp/exec.go`では、`rune`, `rune1`という変数名が`r`, `r1`に変更されています。これは、Goの予約語である`rune`と変数名が衝突するのを避けるため、または単に短縮形にしたものと考えられます。重要なのは、これらの変数の型が`int`から`rune`に変更されている点です。これにより、正規表現のマッチング処理において、個々の文字がUnicodeコードポイントとして正確に扱われるようになります。
2. **`endOfText`定数の型変更**:
* `const endOfText = -1` から `const endOfText rune = -1` へと変更されています。これは、テキストの終端を示す特殊な値も`rune`型として定義することで、型の一貫性を保ち、文字と関連する操作で誤って`int`として扱われることを防ぎます。
3. **`Regexp`構造体のフィールド型変更**:
* `Regexp`構造体の`prefixRune`フィールドが`int`から`rune`に、`Rune`フィールドが`[]int`から`[]rune`に変更されています。これは、正規表現が持つリテラルプレフィックスや文字クラスの内部表現が、Unicodeコードポイントの配列として直接扱われることを意味します。これにより、多バイト文字を含むプレフィックスや文字クラスが正しく処理されます。
4. **`input`インターフェースのメソッドシグネチャ変更**:
* `input`インターフェースの`step`メソッドの戻り値の型が`(rune int, width int)`から`(r rune, width int)`に変更されています。これは、入力テキストから1文字(Unicodeコードポイント)を読み込む際に、その文字が`rune`型として返されることを明確にしています。これにより、下流の処理で文字が常に`rune`として扱われることが保証されます。
5. **`syntax`パッケージ内の文字クラス、関数引数、変数型の変更**:
* `syntax/compile.go`、`syntax/parse.go`、`syntax/perl_groups.go`、`syntax/prog.go`など、正規表現の構文解析、コンパイル、実行に関わる多くのファイルで、文字クラスを表すスライスが`[]int`から`[]rune`に、個々の文字を引数や戻り値とする関数が`int`から`rune`に変更されています。
* 例えば、`parser`構造体の`tmpClass`フィールドが`[]int`から`[]rune`に、`parseEscape`や`parseClassChar`などの関数が`int`を返す代わりに`rune`を返すように変更されています。
* `appendNegatedClass`関数では、`nextLo`の初期値が`0`から`rune('\u0000')`に変更されています。これは、Unicodeの最小コードポイントを`rune`型で明示的に指定することで、文字の範囲を正確に扱うための変更です。
* `appendTable`や`appendNegatedTable`関数では、`unicode.RangeTable`から取得する`lo`, `hi`, `stride`の値が`int`から`rune`にキャストされています。これは、Unicodeの範囲テーブルの情報を`rune`として解釈し、文字クラスの操作に利用するためです。
* `negateClass`関数でも同様に、`nextLo`の初期値が`rune('\u0000')`に変更されています。
* `ranges`構造体の`p`フィールドが`*[]int`から`*[]rune`に変更されています。これは、文字範囲をソートする際の基盤となるデータ型が`rune`の配列であることを示しています。
* `nextRune`, `isalnum`, `unhex`などの文字操作関数も、引数や戻り値の型が`int`から`rune`に変更されています。
これらの変更は、Goの正規表現エンジンがUnicodeの文字をより正確かつ効率的に処理するための重要なステップです。`rune`型を導入することで、文字のエンコーディングやデコードに関する複雑さを内部的に吸収し、開発者が正規表現を扱う際にUnicodeの特性を意識することなく、期待通りの結果を得られるようになります。
## 関連リンク
- Go CL 5302046: [https://golang.org/cl/5302046](https://golang.org/cl/5302046)
## 参考にした情報源リンク
- Go言語の公式ドキュメント:
- [The Go Programming Language Specification - Rune literals](https://go.dev/ref/spec#Rune_literals)
- [The Go Programming Language Specification - String types](https://go.dev/ref/spec#String_types)
- [Go Blog: Strings, bytes, runes and characters in Go](https://go.dev/blog/strings)
- Unicode Consortium: [https://home.unicode.org/](https://home.unicode.org/)
- UTF-8 - Wikipedia: [https://ja.wikipedia.org/wiki/UTF-8](https://ja.wikipedia.org/wiki/UTF-8)
- 正規表現 - Wikipedia: [https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE](https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE)
- Go regexp package documentation: [https://pkg.go.dev/regexp](https://pkg.go.dev/regexp)
- Go unicode package documentation: [https://pkg.go.dev/unicode](https://pkg.go.dev/unicode)
- Go utf8 package documentation: [https://pkg.go.dev/unicode/utf8](https://pkg.go.dev/unicode/utf8)
- Google検索: "golang rune vs int", "golang regexp unicode", "golang regexp internal"