[インデックス 1740] ファイルの概要
このコミットは、Go言語の初期開発段階における重要な変更を示しています。具体的には、Go言語の字句解析器(scanner)とトークン定義(token)を、usr/gri/pretty
という一時的な開発ディレクトリから、より永続的なsrc/lib/lang
ディレクトリへ移動し、それに伴う関連ファイルの調整とテストの追加を行っています。これは、Goコンパイラの基盤となる部分の構造化と整理の一環です。
コミット
commit b4802dd568c7ab79c8cd4da75528a153706014ad
Author: Robert Griesemer <gri@golang.org>
Date: Wed Mar 4 17:13:12 2009 -0800
Created new directory lib/lang:
- move scanner to into lib/lang
- added test
- adjusted various make and build files
R=r
DELTA=1731 (973 added, 753 deleted, 5 changed)
OCL=25668
CL=25713
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b4802dd568c7ab79c8cd4da75528a153706014ad
元コミット内容
新しいディレクトリ lib/lang
を作成し、以下の変更を行いました:
- スキャナーを
lib/lang
に移動 - テストを追加
- 様々な make およびビルドファイルを調整
変更の背景
このコミットは、Go言語のコンパイラおよびツールチェインの初期段階におけるコードベースの整理とモジュール化の一環として行われました。usr/gri/pretty
というディレクトリ名が示すように、これはRobert Griesemer氏の個人開発スペースのようなものであり、そこで開発されていた字句解析器(scanner)とトークン定義(token)が、Go言語のコアライブラリの一部として正式に位置づけられる必要がありました。
lib/lang
という新しいディレクトリは、言語の基本的な要素(字句解析、構文解析、型チェックなど)に関連するコードを格納するための論理的な場所として設計されました。これにより、コードの発見可能性、保守性、および将来的な拡張性が向上します。また、テストの追加は、コードの移動とリファクタリングが正しく行われたことを検証し、将来の変更に対する回帰を防ぐための重要なステップです。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
- 字句解析器(Lexer/Scanner): コンパイラの最初のフェーズであり、ソースコードをトークン(意味のある最小単位)のストリームに変換する役割を担います。例えば、
var x = 10;
というコードは、var
(キーワード)、x
(識別子)、=
(演算子)、10
(整数リテラル)、;
(区切り文字)といったトークンに分割されます。 - トークン(Token): 字句解析器によって生成される、ソースコードの最小単位。各トークンは、その種類(例: キーワード、識別子、演算子)と、元のソースコードにおける値(リテラル)を持ちます。
- Go言語のパッケージ構造: Go言語では、コードはパッケージに整理されます。パッケージは、関連する機能を提供するGoソースファイルの集合です。このコミットでは、
scanner
とtoken
パッケージがlib/lang
という新しいパスに移動されています。 - Makefile: ビルド自動化ツールである
make
が使用する設定ファイルです。プロジェクトのコンパイル、テスト、インストールなどの手順が記述されています。このコミットでは、新しいディレクトリ構造に合わせてMakefileが更新されています。 go.mod
/go.sum
: 現代のGoモジュールシステムにおける依存関係管理ファイルですが、このコミットが行われた2009年時点ではGoモジュールは存在せず、Goのビルドシステムはよりシンプルなものでした。しかし、概念的には、ビルドシステムが新しいファイルパスを認識し、正しくコンパイルできるように調整が必要でした。- UTF-8: Unicode文字を符号化するための可変長文字コードです。Go言語はUTF-8をネイティブにサポートしており、字句解析器が多バイト文字を正しく処理できることが重要です。
unicode
パッケージやutf8
パッケージがそのために使用されます。
技術的詳細
このコミットの技術的な詳細は、主に以下の点に集約されます。
-
ディレクトリ構造の変更:
usr/gri/pretty/scanner.go
がsrc/lib/lang/scanner.go
にリネームされました。usr/gri/pretty/token.go
が削除され、新しいsrc/lib/lang/token.go
が作成されました。これは単なる移動ではなく、token.go
の内容が一部変更されていることを示唆しています(後述)。- 新しいテストファイル
src/lib/lang/scanner_test.go
が追加されました。
-
scanner.go
の変更:- パッケージ宣言は
package scanner
のままですが、ファイルのパスが変わったことで、このパッケージがlib/lang
の一部として認識されるようになります。 - コメント内のインポートパスの例が、
import "token"
からimport "token"
(タブインデントの修正)に変更されていますが、これは単なるフォーマットの変更です。 Scanner
構造体のフィールドコメントがより詳細になり、setup
がimmutable state
に、scanning
がscanning state
に変更されています。is_letter
関数がisLetter
に、digit_val
関数がdigitVal
にリネームされ、Goの命名規則(CamelCase)に準拠しています。isLetter
関数にch >= 0x80 && unicode.IsLetter(ch)
という条件が追加され、ASCII範囲外のUnicode文字も正しく識別子として扱えるようになりました。これはGo言語が多言語対応を重視していることを示しています。digitVal
関数がswitch
文を使用するように変更され、より簡潔になりました。next
関数内のコメントがassume ascii
からassume ASCII
、not ascii
からnot ASCII
に変更されています。expect
関数内のコメントがmake always progress
からalways make progress
に変更されています。scanComment
関数内のgoto exit;
がreturn S.src[pos : S.chpos];
に置き換えられ、goto
の使用が削減されています。これはコードの可読性と保守性の向上に寄与します。select2
,select3
,select4
といったヘルパー関数がswitch2
,switch3
,switch4
にリネームされています。これらの関数は、+=
,>>=
,&&
のような複数文字の演算子を効率的にスキャンするために使用されます。Scan
関数内のgoto loop;
がgoto scan_again;
に、loop:
ラベルがscan_again:
ラベルにそれぞれ変更されています。これはコメントをスキップする際の字句解析器の動作を制御します。
- パッケージ宣言は
-
token.go
の変更:usr/gri/pretty/token.go
が削除され、src/lib/lang/token.go
が新規作成されています。- 新しい
token.go
では、トークン定数(ILLEGAL
,EOF
,IDENT
など)がiota
を使用して定義されており、より簡潔で拡張性の高い方法でトークンを管理しています。 TokenString
関数が、switch
文ではなくmap[int]string
を使用してトークン文字列を返すように変更されています。これにより、新しいトークンが追加された際のメンテナンスが容易になります。Precedence
関数が追加され、演算子の優先順位を返すようになりました。これは、後の構文解析フェーズで重要になります。keywords
マップの初期化ロジックが変更され、TokenString(i)
ではなくtokens[i]
からキーワード文字列を取得するように修正されています。Lookup
関数が、識別子文字列から対応するキーワードトークンを検索するようになりました。以前のバージョンでは[]byte
をstring
に変換してマップのキーとしていましたが、このコミットではその問題に対するコメントが残されています(TODO Maps with []byte key are illegal because []byte does not support == . Should find a more efficient solution eventually.
)。これは、Go言語のマップがスライスをキーとして直接サポートしていなかった初期の制約を示しています。IsLiteral
,IsOperator
,IsKeyword
といった述語関数が追加され、トークンの種類を簡単に判別できるようになりました。
-
ビルドファイルの調整:
src/lib/Makefile
にlang
ディレクトリが追加され、lang.dirinstall
ターゲットがstrconv.dirinstall utf8.install unicode.dirinstall
に依存するように設定されました。これは、scanner
とtoken
パッケージがこれらのパッケージに依存しているためです。src/run.bash
にもlib/lang
がテスト対象として追加されました。usr/gri/pretty/Makefile
が大幅に修正され、token.6
やscanner.6
への直接的な依存関係が削除され、新しいlib/lang
の構造を反映するように変更されました。これは、pretty
ディレクトリがもはやscanner
とtoken
のソースではないことを意味します。
コアとなるコードの変更箇所
src/lib/lang/scanner.go
(旧 usr/gri/pretty/scanner.go
)
// 変更前:
// func is_letter(ch int) bool { ... }
// func digit_val(ch int) int { ... }
// func (S *Scanner) select2(tok0, tok1 int) int { ... }
// func (S *Scanner) select3(tok0, tok1, ch2, tok2 int) int { ... }
// func (S *Scanner) select4(tok0, tok1, ch2, tok2, tok3 int) int { ... }
// goto loop;
// 変更後:
func isLetter(ch int) bool {
return
'a' <= ch && ch <= 'z' ||
'A' <= ch && ch <= 'Z' ||
ch == '_' ||
ch >= 0x80 && unicode.IsLetter(ch); // Unicode文字のサポート
}
func digitVal(ch int) int {
switch {
case '0' <= ch && ch <= '9': return ch - '0';
case 'a' <= ch && ch <= 'f': return ch - 'a' + 10;
case 'A' <= ch && ch <= 'F': return ch - 'A' + 10;
}
return 16;
}
func (S *Scanner) switch2(tok0, tok1 int) int { ... } // select2からリネーム
func (S *Scanner) switch3(tok0, tok1, ch2, tok2 int) int { ... } // select3からリネーム
func (S *Scanner) switch4(tok0, tok1, ch2, tok2, tok3 int) int { ... } // select4からリネーム
// ...
// if !S.scan_comments {
// goto scan_again; // loopからscan_againにリネーム
// }
src/lib/lang/token.go
(新規作成)
package token
import "strconv"
const (
// Special tokens
ILLEGAL = iota;
EOF;
COMMENT;
// Identifiers and basic type literals
literal_beg;
IDENT;
INT;
FLOAT;
CHAR;
STRING;
literal_end;
// Operators and delimiters
operator_beg;
ADD;
// ... (他の演算子)
operator_end;
// Keywords
keyword_beg;
BREAK;
// ... (他のキーワード)
keyword_end;
)
var tokens = map [int] string { // mapを使用したトークン文字列定義
ILLEGAL : "ILLEGAL",
EOF : "EOF",
COMMENT : "COMMENT",
// ...
}
func TokenString(tok int) string { // mapから文字列を取得
if str, exists := tokens[tok]; exists {
return str;
}
return "token(" + strconv.Itoa(tok) + ")";
}
const (
LowestPrec = -1;
UnaryPrec = 7;
HighestPrec = 8;
)
func Precedence(tok int) int { // 演算子の優先順位を定義
switch tok {
case COLON:
return 0;
// ...
}
return LowestPrec;
}
var keywords map [string] int;
func init() { // キーワードマップの初期化
keywords = make(map [string] int);
for i := keyword_beg + 1; i < keyword_end; i++ {
keywords[tokens[i]] = i;
}
}
func Lookup(ident []byte) int { // 識別子からキーワードを検索
if tok, is_keyword := keywords[string(ident)]; is_keyword {
return tok;
}
return IDENT;
}
// Predicates
func IsLiteral(tok int) bool { ... }
func IsOperator(tok int) bool { ... }
func IsKeyword(tok int) bool { ... }
src/lib/lang/scanner_test.go
(新規作成)
// Copyright 2009 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.
package scanner
import (
"io";
"token";
"scanner";
"testing";
)
// ... (テスト用のヘルパー関数とデータ構造)
func Test(t *testing.T) {
// make source
var src string;
for i, e := range tokens {
src += e.lit + " ";
}
// set up scanner
var s scanner.Scanner;
s.Init(io.StringBytes(src), &TestErrorHandler{t}, true);
// verify scan
for i, e := range tokens {
pos, tok, lit := s.Scan();
// ... (アサーション)
}
// ... (EOFチェック)
}
コアとなるコードの解説
このコミットの核心は、Go言語の字句解析器とトークン定義の構造化と堅牢化にあります。
scanner.go
の変更では、関数名のGo命名規則への準拠(is_letter
-> isLetter
など)と、Unicode文字の識別子としてのサポートが特に重要です。これにより、Go言語が国際化されたコードベースをより適切に扱えるようになります。また、goto
文の削減は、コードのフローをより直線的にし、理解しやすくするための一般的なベストプラクティスです。
token.go
の新規作成は、トークン管理の大きな改善を示しています。iota
の使用によるトークン定数の定義は、新しいトークンを追加する際の作業を簡素化し、エラーを減らします。TokenString
関数がマップを使用するように変更されたことで、トークンとそれに対応する文字列表現の間のマッピングがより明確になり、保守が容易になります。Precedence
関数の導入は、コンパイラの次の段階である構文解析において、演算子の結合規則を正しく処理するために不可欠です。IsLiteral
, IsOperator
, IsKeyword
といった述語関数は、トークンの分類を抽象化し、コードの可読性を向上させます。
scanner_test.go
の追加は、この変更セットの信頼性を高める上で極めて重要です。字句解析器のようなコンパイラの基盤部分は、非常に正確でなければなりません。テストケースは、様々な種類のトークン(コメント、識別子、リテラル、演算子、キーワード)が正しく認識されることを検証し、将来の変更が既存の機能に悪影響を与えないことを保証します。
全体として、これらの変更は、Go言語のコンパイラがより整理され、堅牢で、将来の拡張に対応できるような基盤を構築するための初期の重要なステップでした。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の字句解析器に関する一般的な情報: https://go.dev/blog/go-parser (このブログ記事はコミットより後のものですが、Goのパーサーとスキャナーの設計思想を理解するのに役立ちます)
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語の初期のコミット履歴: https://github.com/golang/go/commits/master?after=b4802dd568c7ab79c8cd4da75528a153706014ad+34&branch=master&path%5B%5D=src%2Flib%2Flang
- Go言語の初期のメーリングリストアーカイブ (Goの設計に関する議論が含まれている可能性があります): https://groups.google.com/g/golang-nuts (具体的な議論を見つけるには検索が必要)
- コンパイラの設計に関する一般的な書籍やオンラインリソース (例: Dragon Book)
- Go言語の命名規則: https://go.dev/doc/effective_go#names
- Go言語の
iota
に関するドキュメント: https://go.dev/ref/spec#Iota - Go言語の
map
に関するドキュメント: https://go.dev/ref/spec#Map_types - Go言語の
unicode
パッケージ: https://pkg.go.dev/unicode - Go言語の
utf8
パッケージ: https://pkg.go.dev/unicode/utf8