Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1890] ファイルの概要

このコミットは、Go言語の字句解析器(scanner)と構文解析器(parser)におけるトークン表現の根本的な改善を目的としています。具体的には、これまで整数型(int)で表現されていたトークンを、明示的なtoken.Token型として導入し、関連するユーティリティ関数をtoken.Token型のメソッドに変換しています。これにより、コードの型安全性、可読性、保守性が向上しています。

コミット

commit cc8e4fb4854a218b2bc51db8bda370a0154a18a1
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Mar 26 10:53:14 2009 -0700

    - introduce explicit Token type
    - convert some functions into methods
    - corresponding changes in pretty
    
    R=r
    DELTA=57  (3 added, 0 deleted, 54 changed)
    OCL=26764
    CL=26777

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/cc8e4fb4854a218b2bc51db8bda370a0154a18a1

元コミット内容

    - introduce explicit Token type
    - convert some functions into methods
    - corresponding changes in pretty

変更の背景

Go言語の初期開発段階において、字句解析器や構文解析器が扱う「トークン」は、単なる整数値(int)として表現されていました。これは、C言語などの影響を受けた初期の実装では一般的なアプローチです。しかし、この方法にはいくつかの欠点があります。

  1. 型安全性の欠如: int型は汎用的な型であるため、誤ってトークンではない整数値がトークンとして扱われたり、その逆の操作が行われたりする可能性があります。コンパイラはこのような型ミスマッチを検出できません。
  2. 可読性の低下: コードを読んだ際に、intがトークンを表しているのか、それとも別の数値データを表しているのかが直感的に分かりにくい場合があります。
  3. コードの分散: トークンに関連する操作(例: トークンを文字列に変換する、トークンの種類を判定する)が、トークン自体とは独立したグローバル関数として定義される傾向にあります。これにより、トークンに関するロジックがコードベースの様々な場所に分散し、保守性が低下します。

このコミットは、これらの問題を解決し、Go言語の設計思想である「シンプルさ」と「安全性」を字句解析・構文解析のコア部分にも適用するために行われました。明示的なToken型を導入し、関連する操作をその型のメソッドとしてカプセル化することで、より堅牢で理解しやすいコードベースを目指しています。

前提知識の解説

このコミットを理解するためには、以下の概念についての知識が役立ちます。

  • 字句解析 (Lexical Analysis / Scanning): プログラムのソースコードを、意味のある最小単位である「トークン」の並びに分解するプロセスです。例えば、if x == 10 {というコードは、if (キーワード), x (識別子), == (演算子), 10 (整数リテラル), { (デリミタ) といったトークンに分解されます。Go言語では、scannerパッケージがこの役割を担います。
  • 構文解析 (Syntactic Analysis / Parsing): 字句解析によって生成されたトークンの並びが、言語の文法規則に合致しているかを検証し、通常は抽象構文木(AST: Abstract Syntax Tree)を構築するプロセスです。Go言語では、go/parserパッケージがこの役割を担います。
  • トークン (Token): 字句解析によって識別される、プログラムの最小単位です。キーワード(if, for)、識別子(変数名、関数名)、リテラル(数値、文字列)、演算子(+, -, ==)、デリミタ((, ), {, })などがあります。
  • 型安全性 (Type Safety): プログラムが不正な型の操作を行わないことを保証する特性です。型安全な言語では、コンパイル時または実行時に型エラーが検出され、予期せぬ動作を防ぎます。
  • メソッド (Methods): 特定の型に関連付けられた関数です。Go言語では、レシーバ引数(func (t MyType) MyMethod(...))を持つ関数として定義され、その型のインスタンスに対して呼び出されます。メソッドを使用することで、データ(型)とそのデータに対する操作(メソッド)を一緒に定義でき、オブジェクト指向的な設計が可能になります。
  • iota: Go言語のキーワードで、連続する定数に自動的にインクリメントされる値を割り当てるために使用されます。通常、列挙型(enum)のような定数セットを定義する際に便利です。

技術的詳細

このコミットの核となる技術的変更は、src/lib/go/token/token.goファイルにおけるToken型の導入と、それに伴う関連ファイルの変更です。

  1. token.Token型の導入:

    • type Token intという新しい型が定義されました。これにより、Go言語のトークンは単なるintではなく、Tokenという専用の型を持つことになります。
    • 既存のトークン定数(ILLEGAL, EOF, IDENTなど)は、ILLEGAL = iota;からILLEGAL Token = iota;のように、明示的にToken型として宣言されるようになりました。これにより、これらの定数がToken型であることをコンパイラが認識し、型チェックが強化されます。
  2. 関数のメソッド化:

    • これまでint型のトークンを受け取っていたグローバル関数(例: TokenString(tok int) string, Precedence(tok int) int, IsLiteral(tok int) boolなど)が、Token型のメソッドに変換されました。
    • 例えば、func TokenString(tok int) stringfunc (tok Token) String() stringに、func Precedence(tok int) intfunc (tok Token) Precedence() intに変わりました。
    • これにより、トークンに関する操作はmyToken.String()myToken.IsLiteral()のように、Token型のインスタンスから直接呼び出せるようになり、コードの凝集度が高まりました。
  3. scannerパッケージの変更:

    • src/lib/go/scanner/scanner.goでは、字句解析の結果としてトークンを返す関数の戻り値の型がintからtoken.Tokenに変更されました。
    • 例えば、scanIdentifier()scanNumber()Scan()メソッドの戻り値の型が更新されています。
    • また、Tokenize関数のコールバック関数の引数もintからtoken.Tokenに変更されています。
  4. prettyパッケージ(初期のAST/パーサー関連コード)の変更:

    • usr/gri/pretty/ast.go, usr/gri/pretty/parser.go, usr/gri/pretty/printer.goといったファイルでは、抽象構文木(AST)のノードやパーサー、プリンターがトークンを扱う部分で、int型がtoken.Token型に置き換えられました。
    • これにより、ASTの構造やパーサーの内部ロジック、プリンターの出力処理において、型安全なトークン表現が使用されるようになりました。特に、ast.UnaryExpr, ast.BinaryExpr, ast.IncDecStmt, ast.AssignStmt, ast.BranchStmt, ast.CommClause, ast.DeclListなどの構造体内のTokフィールドの型がintからtoken.Tokenに変更されています。
    • パーサー内のexpect関数やparseBranchStmt関数、parseCommClause関数、そしてプリンター内のToken関数やError関数も、引数やローカル変数の型がtoken.Tokenに更新されています。

これらの変更により、Go言語のコンパイラツールチェーンの基盤部分がより堅牢で、将来の拡張にも対応しやすい設計へと進化しました。

コアとなるコードの変更箇所

このコミットの最も重要な変更は、src/lib/go/token/token.goファイルにあります。

--- a/src/lib/go/token.go
+++ b/src/lib/go/token.go
@@ -10,10 +10,13 @@ package token
 
 import "strconv"
 
+// Token is the set of lexical tokens of the Go programming language.
+type Token int
+
 // The list of tokens.
 const (
 	// Special tokens
-	ILLEGAL = iota;
+	ILLEGAL Token = iota;
 	EOF;
 	COMMENT;
 	
@@ -124,7 +127,7 @@ const (
 // At the moment we have no array literal syntax that lets us describe
 // the index for each element - use a map for now to make sure they are
 // in sync.
-var tokens = map [int] string {
+var tokens = map [Token] string {
 	ILLEGAL : "ILLEGAL",
 
 	EOF : "EOF",
@@ -224,13 +227,13 @@ var tokens = map [int] string {
 }
 
 
-// TokenString returns the string corresponding to the token tok.\n+// String returns the string corresponding to the token tok.\n // For operators, delimiters, and keywords the string is the actual\n // token character sequence (e.g., for the token ADD, the string is\n // "+"). For all other tokens the string corresponds to the token\n // constant name (e.g. for the token IDENT, the string is "IDENT").\n //
-func TokenString(tok int) string {
+func (tok Token) String() string {
 	if str, exists := tokens[tok]; exists {
 		return str;
 	}
@@ -254,7 +257,7 @@ const (
 // Precedence returns the syntax precedence of the operator
 // token tok or LowestPrecedence if tok is not an operator.
 //
-func Precedence(tok int) int {
+func (tok Token) Precedence() int {
 	switch tok {
 	case COLON:
 		return 0;
@@ -275,10 +278,10 @@ func Precedence(tok int) int {
 }
 
 
-var keywords map [string] int;
+var keywords map [string] Token;
 
 func init() {
-	keywords = make(map [string] int);
+	keywords = make(map [string] Token);
 	for i := keyword_beg + 1; i < keyword_end; i++ {
 		keywords[tokens[i]] = i;
 	}
@@ -287,20 +290,20 @@ func init() {
 
 // Lookup maps an identifier to its keyword token or IDENT (if not a keyword).
 //
-func Lookup(ident []byte) int {
+func Lookup(ident []byte) Token {
 	// TODO Maps with []byte key are illegal because []byte does not
 	//      support == . Should find a more efficient solution eventually.
 	if tok, is_keyword := keywords[string(ident)]; is_keyword {
 		return tok;
 	}
 	return IDENT;
 }
 
 // IsLiteral returns true for tokens corresponding to identifiers
 // and basic type literals; returns false otherwise.
 //
-func IsLiteral(tok int) bool {
+func (tok Token) IsLiteral() bool {
 	return literal_beg < tok && tok < literal_end;
 }
 
 // IsOperator returns true for tokens corresponding to operators and
 // delimiters; returns false otherwise.
 //
-func IsOperator(tok int) bool {
+func (tok Token) IsOperator() bool {
 	return operator_beg < tok && tok < operator_end;
 }
 
 // IsKeyword returns true for tokens corresponding to keywords;
 // returns false otherwise.
 //
-func IsKeyword(tok int) bool {
+func (tok Token) IsKeyword() bool {
 	return keyword_beg < tok && tok < keyword_end;
 }

コアとなるコードの解説

上記の差分は、src/lib/go/token/token.goファイルにおける主要な変更を示しています。

  1. type Token intの定義:

    • // Token is the set of lexical tokens of the Go programming language. というコメントとともに、Tokenという新しい整数型が定義されています。これにより、Go言語の字句解析器が生成するすべてのトークンは、このToken型を持つことになります。これは、単なるintではなく、特定の意味を持つ型として扱われることを意味します。
  2. トークン定数の型指定:

    • const ( ILLEGAL Token = iota; ... ) のように、ILLEGALなどのトークン定数に明示的にToken型が割り当てられています。これにより、これらの定数がToken型であることをコンパイラが認識し、型チェックが厳密になります。例えば、int型の変数にtoken.ILLEGALを直接代入しようとすると、型エラーになる可能性があります(Goの型変換ルールによりますが、意図しない代入を防ぐ効果があります)。
  3. tokensマップのキー型の変更:

    • var tokens = map [int] stringvar tokens = map [Token] string に変更されています。これは、トークン定数からその文字列表現を取得するためのマップのキーが、新しいToken型になったことを示しています。
  4. TokenString関数のString()メソッドへの変換:

    • func TokenString(tok int) stringfunc (tok Token) String() string に変更されました。これは、Token型のインスタンスが自身の文字列表現を返すためのメソッドを持つようになったことを意味します。これにより、myToken.String()のように、トークンオブジェクト自体からその文字列表現を取得できるようになり、コードがよりオブジェクト指向的で読みやすくなります。
  5. Precedence関数のメソッド化:

    • func Precedence(tok int) intfunc (tok Token) Precedence() int に変更されました。これにより、演算子トークンの優先順位を、そのトークンオブジェクトから直接取得できるようになりました。
  6. keywordsマップのバリュー型の変更:

    • var keywords map [string] intvar keywords map [string] Token に変更されています。これは、キーワードの文字列から対応するトークン定数を取得するためのマップのバリューが、新しいToken型になったことを示しています。
  7. Lookup関数の戻り値型の変更:

    • func Lookup(ident []byte) intfunc Lookup(ident []byte) Token に変更されました。これは、識別子文字列から対応するトークン(キーワードまたは識別子トークン)を返す関数の戻り値が、新しいToken型になったことを示しています。
  8. IsLiteral, IsOperator, IsKeyword関数のメソッド化:

    • func IsLiteral(tok int) bool, func IsOperator(tok int) bool, func IsKeyword(tok int) bool がそれぞれ func (tok Token) IsLiteral() bool, func (tok Token) IsOperator() bool, func (tok Token) IsKeyword() bool に変更されました。これにより、トークンがリテラル、演算子、キーワードのいずれであるかを判定するロジックが、Token型のメソッドとしてカプセル化され、より自然な呼び出し方(例: myToken.IsLiteral())が可能になりました。

これらの変更は、Go言語の字句解析器と構文解析器の内部実装において、トークンの扱いをより厳密で型安全なものにし、コードの構造を改善する上で非常に重要なステップでした。

関連リンク

参考にした情報源リンク