[インデックス 1840] ファイルの概要
このコミットは、Go言語の抽象構文木(AST)、パーサー、およびプリティプリンターの初期段階における大規模なクリーンアップと再構築を目的としています。特に、ASTノードの構造を整理し、より正確な位置情報(Position
)の導入、新しい言語構造に対応するASTノードの追加、および既存ノードのセマンティクスを改善することに焦点を当てています。これにより、Go言語の構文解析とコード生成の基盤が強化され、将来の言語機能の追加やツール開発が容易になるように設計されています。
コミット
commit 592dbb2d0a50ace6095c6d30e3d3d6c36991ab15
Author: Robert Griesemer <gri@golang.org>
Date: Tue Mar 17 18:41:35 2009 -0700
daily snapshot:
- first part of AST cleaned up and documented
- tons of related cleanups and adjustments
R=r
OCL=26430
CL=26430
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/592dbb2d0a50ace6095c6d30e3d3d6c36991ab15
元コミット内容
daily snapshot:
- first part of AST cleaned up and documented
- tons of related cleanups and adjustments
R=r
OCL=26430
CL=26430
変更の背景
このコミットは、Go言語がまだ開発の初期段階にあった2009年に行われました。当時のGoコンパイラは、言語仕様が固まっていない中で頻繁な変更に対応する必要がありました。このような状況では、コンパイラのフロントエンド、特に抽象構文木(AST)の設計が非常に重要になります。
変更の主な背景は以下の通りです。
- ASTの整理と標準化: 初期段階のASTは、試行錯誤の中で設計されており、一貫性や明確さに欠ける部分がありました。このコミットは、ASTノードの命名規則、フィールドの構造、および位置情報の扱いを標準化し、より堅牢な基盤を構築することを目指しました。
- 言語機能の進化への対応: Go言語は当時も進化を続けており、新しい構文や表現が導入されていました。例えば、複合リテラル(
CompositeLit
)、スライス式(Slice
)、型アサーション(TypeAssertion
)などは、言語の表現力を高めるために必要不可欠な要素でした。これらの新しい構造を正確に表現できるASTノードの追加が求められました。 - パーサーとプリティプリンターの改善: ASTの変更に伴い、それを構築するパーサーと、ASTを元にコードを整形するプリティプリンターも更新する必要がありました。特に、パーサーは新しいAST構造を正しく生成し、プリティプリンターは整形されたコードを正確に出力できるように調整されました。
- コード品質の向上と保守性の確保:
Loc_
からPos_
への変更や、ExprVisitor
インターフェースの整理は、コードの可読性と保守性を向上させるためのものです。これにより、将来の機能追加やバグ修正が容易になります。
これらの変更は、Go言語のコンパイラがより安定し、将来の発展に対応できるような基盤を築く上で不可欠なステップでした。
前提知識の解説
このコミットの変更内容を理解するためには、以下の概念を把握しておく必要があります。
-
抽象構文木(Abstract Syntax Tree: AST):
- プログラミング言語のソースコードを、その構文構造に基づいて抽象的に表現した木構造のデータ構造です。コンパイラのフロントエンドにおいて、字句解析(Lexical Analysis)と構文解析(Syntactic Analysis)の後に生成されます。
- ASTは、ソースコードの意味を保持しつつ、コメントや空白などの構文上重要でない詳細を排除します。これにより、コンパイラの次の段階(意味解析、最適化、コード生成など)でコードを効率的に処理できるようになります。
- Go言語のASTは、
go/ast
パッケージで定義されており、Expr
(式)、Stmt
(文)、Decl
(宣言)などのインターフェースと、それらを実装する具体的なノード(例:ast.Ident
、ast.BinaryExpr
、ast.FuncDecl
)で構成されます。
-
字句解析器(Scanner/Lexer):
- ソースコードを読み込み、意味のある最小単位である「トークン(Token)」のストリームに変換するコンポーネントです。例えば、
var x = 10
というコードは、var
(キーワード)、x
(識別子)、=
(演算子)、10
(リテラル)といったトークンに分割されます。 - Go言語では、
go/scanner
パッケージがこの機能を提供し、scanner.Location
(現在のコミットではscanner.Position
に相当)はソースコード内のトークンの位置情報(ファイル名、行番号、列番号など)を表します。
- ソースコードを読み込み、意味のある最小単位である「トークン(Token)」のストリームに変換するコンポーネントです。例えば、
-
構文解析器(Parser):
- 字句解析器が生成したトークンのストリームを受け取り、言語の文法規則に従ってASTを構築するコンポーネントです。パーサーは、トークンの並びが文法的に正しいかどうかを検証し、エラーがあれば報告します。
- このコミットで変更されている
usr/gri/pretty/parser.go
は、Go言語の初期のパーサーの実装の一部です。
-
プリティプリンター(Pretty Printer):
- ASTを受け取り、整形されたソースコードを生成するコンポーネントです。コンパイラの一部として、またはコードフォーマッター(例:
gofmt
)として使用されます。 - このコミットで変更されている
usr/gri/pretty/printer.go
は、Go言語の初期のプリティプリンターの実装の一部です。
- ASTを受け取り、整形されたソースコードを生成するコンポーネントです。コンパイラの一部として、またはコードフォーマッター(例:
-
Visitorパターン:
- オブジェクト構造の要素に対して実行される操作を、その構造から分離することを可能にするデザインパターンです。ASTのような複雑な木構造を走査し、各ノードに対して特定の処理を行う際に非常に有用です。
- Go言語のASTでは、
ast.Walk
関数とast.Visitor
インターフェースがこのパターンを実装しており、ASTの各ノードを訪問して処理を行うことができます。このコミットでは、ExprVisitor
インターフェースがこの役割を担っています。
技術的詳細
このコミットにおける技術的な変更は多岐にわたりますが、主要なポイントは以下の通りです。
-
位置情報の統一と明確化 (
Loc_
からPos_
へ):- 以前はASTノードのソースコード上の位置情報を示すフィールド名が
Loc_
でしたが、このコミットでPos_
に統一されました。これは、scanner.Location
がPosition
という型エイリアスに置き換えられたことと合わせて、より「位置」を明確に指し示す意図があります。 Expr
インターフェースのLoc()
メソッドもPos()
メソッドに変更され、すべての式ノードが統一された方法で位置情報を提供するようになりました。これにより、ASTを走査するツールやデバッガーが、コードのどの部分に対応するASTノードであるかを容易に特定できるようになります。
- 以前はASTノードのソースコード上の位置情報を示すフィールド名が
-
ASTノードの再設計と新規追加:
StringLit
の導入: 以前は複数の文字列リテラルがConcatExpr
として扱われていましたが、このコミットでStringLit
という専用のノードが導入されました。これは、Go言語が複数の隣接する文字列リテラルを自動的に連結する機能(例:"hello" + "world"
ではなく"hello" "world"
)を持つため、これをASTでより正確に表現するためです。ConcatExpr
は削除されました。CompositeLit
の導入: 複合リテラル(例:[]int{1, 2, 3}
やmap[string]int{"a": 1}
)を表現するためのCompositeLit
ノードが追加されました。これにより、構造体、配列、スライス、マップの初期化構文がASTで明確に表現できるようになりました。Slice
の導入: スライス式(例:arr[low:high]
)を表現するためのSlice
ノードが追加されました。以前はIndex
ノードで部分的に扱われていた可能性がありましたが、スライス専用のノードを設けることで、そのセマンティクスが明確になりました。TypeAssertion
の導入: 型アサーション(例:x.(type)
)を表現するためのTypeAssertion
ノードが追加されました。以前のTypeGuard
ノードがこれに置き換えられました。- 既存ノードのフィールド変更:
Ident
ノードは、Loc_
フィールドがPos_
に、Str
フィールドが先にくるように順序が変更されました。FunctionLit
ノードは、Func
というPosition
フィールドが追加され、func
キーワードの位置を明示的に保持するようになりました。Group
ノードは、Lparen
とRparen
というPosition
フィールドが追加され、括弧の位置を保持するようになりました。Selector
、Index
、Call
、UnaryExpr
、BinaryExpr
ノードも同様に、関連するキーワードや記号の位置を保持するためのPosition
フィールドが追加されました。これにより、ASTノードがソースコード上のより多くの情報を保持できるようになり、エラー報告やツール開発の精度が向上します。
-
ExprVisitor
インターフェースの更新:- 新しいASTノード(
DoStringLit
,DoCompositeLit
,DoSlice
,DoTypeAssertion
)に対応するDo
メソッドが追加されました。 - 削除されたノード(
DoConcatExpr
,DoTypeGuard
)に対応するメソッドは削除されました。 - 既存のノード(
DoBinaryExpr
,DoUnaryExpr
)のメソッドシグネチャが、新しいAST構造に合わせて調整されました。これにより、ASTを走査して特定の処理を行う際に、すべての式ノードに対して一貫した方法でアクセスできるようになります。
- 新しいASTノード(
-
パーサーロジックの調整:
parser.go
では、ASTノードの変更に合わせて、それらを構築するロジックが全面的に修正されました。例えば、parseStringLit
関数は複数の文字列リテラルをStringLit
ノードとして正しく解析するように変更され、parseSelectorOrTypeGuard
はparseSelectorOrTypeAssertion
に、parseIndex
はparseIndexOrSlice
にそれぞれ改名され、新しいASTノードを生成するように更新されました。parseCallOrCompositeLit
のような汎用的な解析関数が、parseCall
とparseCompositeLit
のように、より具体的な関数に分割されました。これは、コードのモジュール性と可読性を向上させるための良いプラクティスです。- エラー報告の際に、
Loc()
の代わりにPos()
を使用するように変更されました。
-
プリティプリンターの調整:
printer.go
では、新しいASTノードの出力ロジックが追加され、既存のノードの出力ロジックも変更されました。例えば、DoStringLit
、DoCompositeLit
、DoSlice
、DoTypeAssertion
などの新しいDo
メソッドが実装されました。DoBinaryExpr
やDoUnaryExpr
では、以前の特殊なケース(カンマ区切りのリストやrange
キーワード)の処理が削除され、より一般的なAST構造に対応するように簡素化されました。これは、ASTがより詳細な情報を保持するようになったため、プリティプリンター側で特殊なロジックを減らせるようになったことを示唆しています。Loc_
からPos_
への変更に伴い、P.String(x.Loc_, ...)
のような呼び出しがP.String(x.Pos_, ...)
に変更されました。
これらの変更は、Go言語のコンパイラの基盤をより堅牢で拡張性の高いものにするための重要なステップであり、言語の進化とツールの開発を支える上で不可欠なものでした。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主にusr/gri/pretty/ast.go
、usr/gri/pretty/parser.go
、usr/gri/pretty/printer.go
の3つのファイルにまたがっています。
usr/gri/pretty/ast.go
ASTノードの定義とExprVisitor
インターフェースの変更が中心です。
// 変更前:
// type Position scanner.Location // TODO rename scanner.Location to scanner.Position, possibly factor out
// type Expr interface { Loc() scanner.Location; Visit(v ExprVisitor); };
// type BadExpr struct { Loc_ scanner.Location; };
// type Ident struct { Loc_ scanner.Location; Str string; };
// type BinaryExpr struct { Loc_ scanner.Location; Tok int; X, Y Expr; };
// type UnaryExpr struct { Loc_ scanner.Location; Tok int; X Expr; };
// type ConcatExpr struct { X, Y Expr; };
// type BasicLit struct { Loc_ scanner.Location; Tok int; Val []byte; };
// type FunctionLit struct { Loc_ scanner.Location; Typ *Signature; Body *Block; };
// type Group struct { Loc_ scanner.Location; X Expr; };
// type Selector struct { Loc_ scanner.Location; X Expr; Sel *Ident; };
// type TypeGuard struct { Loc_ scanner.Location; X Expr; Typ Expr; };
// type Index struct { Loc_ scanner.Location; X, I Expr; };
// type Call struct { Loc_ scanner.Location; Tok int; F, Args Expr };
// 変更後:
type Position scanner.Location // TODO rename scanner.Location to scanner.Position, possibly factor out
// All expression nodes implement the Expr interface.
type Expr interface {
// For a (dynamic) node type X, calling Visit with an expression
// visitor v invokes the node-specific DoX function of the visitor.
//
Visit(v ExprVisitor);
// Pos returns the (beginning) position of the expression.
Pos() Position;
};
// An expression is represented by a tree consisting of one
// or several of the following concrete expression nodes.
//
type (
// A BadExpr node is a placeholder node for expressions containing
// syntax errors for which not correct expression tree can be created.
//
BadExpr struct {
Pos_ Position; // bad expression position
};
// An Ident node represents an identifier (identifier).
Ident struct {
Str string; // identifier string (e.g. foobar)
Pos_ Position; // identifier position
};
// An basic literal is represented by a BasicLit node.
BasicLit struct {
Tok int; // literal token
Lit []byte; // literal string
Pos_ Position; // literal string position
};
// A sequence of string literals (StringLit) is represented
// by a StringLit node.
//
StringLit struct {
Strings []*BasicLit; // sequence of strings
};
// A function literal (FunctionLit) is represented by a FunctionLit node.
FunctionLit struct {
Typ *Signature; // function signature
Body *Block; // function body
Func Position; // position of "func" keyword
};
// A composite literal (CompositeLit) is represented by a CompositeLit node.
CompositeLit struct {
Typ Expr; // literal type
Elts []Expr; // list of composite elements
Lbrace, Rbrace Position; // positions of "{" and "}"
};
// A parenthesized expression is represented by a Group node.
Group struct {
X Expr; // parenthesized expression
Lparen, Rparen Position; // positions of "(" and ")"
};
// A primary expression followed by a selector is represented
// by a Selector node.
//
Selector struct {
X Expr; // primary expression
Sel *Ident; // field selector
Period Position; // position of "."
};
// A primary expression followed by an index is represented
// by an Index node.
//
Index struct {
X Expr; // primary expression
Index Expr; // index expression
Lbrack, Rbrack Position; // positions of "[" and "]"
};
// A primary expression followed by a slice is represented
// by a Slice node.
//
Slice struct {
X Expr; // primary expression
Beg, End Expr; // slice range
Lbrack, Colon, Rbrack Position; // positions of "[" , ":", and "]"
};
// A primary expression followed by a type assertion is represented
// by a TypeAssertion node.
//
TypeAssertion struct {
X Expr; // primary expression
Typ Expr; // asserted type
Period, Lparen, Rparen Position; // positions of ".", "(", and ")"
};
// A primary expression followed by an argument list is represented
// by a Call node.
//
Call struct {
Fun Expr; // function expression
Args []Expr; // function arguments
Lparen, Rparen Position; // positions of "(" and ")"
};
// A unary expression (UnaryExpr) is represented by a UnaryExpr node.
UnaryExpr struct {
Op int; // operator token
X Expr; // operand
Pos_ Position; // operator position
};
// A binary expression (BinaryExpr) is represented by a BinaryExpr node.
BinaryExpr struct {
Op int; // operator token
X, Y Expr; // left and right operand
Pos_ Position; // operator position
};
)
type ExprVisitor interface {
// Expressions
DoBadExpr(x *BadExpr);
DoIdent(x *Ident);
DoBasicLit(x *BasicLit);
DoStringLit(x *StringLit); // 新規
DoFunctionLit(x *FunctionLit);
DoCompositeLit(x *CompositeLit); // 新規
DoGroup(x *Group);
DoSelector(x *Selector);
DoIndex(x *Index);
DoSlice(x *Slice); // 新規
DoTypeAssertion(x *TypeAssertion); // 新規 (TypeGuardから変更)
DoCall(x *Call);
DoUnaryExpr(x *UnaryExpr);
DoBinaryExpr(x *BinaryExpr);
// ... (他のDoメソッドもPos()を使うように変更)
}
// Pos() メソッドの変更例
// 変更前: func (x *BadExpr) Loc() scanner.Location { return x.Loc_; }
// 変更後: func (x *BadExpr) Pos() Position { return x.Pos_; }
usr/gri/pretty/parser.go
ASTノードの構築ロジックの変更と、Position
型への移行が中心です。
// 変更前:
// func (P *Parser) Init(scanner *scanner.Scanner, err scanner.ErrorHandler, trace bool) { ... P.loc scanner.Location; ... }
// func (P *Parser) error(loc scanner.Location, msg string) { ... }
// func (P *Parser) expect(tok int) { ... }
// func (P *Parser) parseIdent() *ast.Ident { ... return &ast.Ident{P.loc, string(P.val)}; ... }
// func (P *Parser) parseQualifiedIdent() ast.Expr { ... x = &ast.Selector{loc, x, y}; ... }
// func (P *Parser) parseFunctionLit() ast.Expr { ... return &ast.FunctionLit{loc, typ, body}; ... }
// func (P *Parser) parseStringLit() ast.Expr { ... var x ast.Expr = &ast.BasicLit{P.loc, P.tok, P.val}; ... x = &ast.ConcatExpr{x, y}; ... return x; }
// func (P *Parser) parseOperand() ast.Expr { ... x := &ast.BasicLit{P.loc, P.tok, P.val}; ... return &ast.Group{loc, x}; ... }
// func (P *Parser) parseSelectorOrTypeGuard(x ast.Expr) ast.Expr { ... x = &ast.Selector{loc, x, P.parseIdent()}; ... x = &ast.TypeGuard{loc, x, typ}; ... }
// func (P *Parser) parseIndex(x ast.Expr) ast.Expr { ... return &ast.Index{loc, x, i}; ... }
// func (P *Parser) parseCallOrCompositeLit(f ast.Expr, open, close int) ast.Expr { ... return &ast.Call{loc, open, f, args}; ... }
// func (P *Parser) parseUnaryExpr() ast.Expr { ... return &ast.UnaryExpr{loc, tok, y}; ... }
// func (P *Parser) parseBinaryExpr(prec1 int) ast.Expr { ... return &ast.BinaryExpr{loc, tok, x, y}; ... }
// 変更後:
type Position scanner.Location // TODO rename Position to scanner.Position, possibly factor out
type Parser struct {
// ...
loc Position; // token location
// ...
}
func (P *Parser) error(loc Position, msg string) {
P.err.Error(loc, msg);
}
func (P *Parser) expect(tok int) Position { // Positionを返すように変更
// ...
loc := P.loc;
P.next();
return loc;
}
func (P *Parser) parseIdent() *ast.Ident {
// ...
if P.tok == token.IDENT {
x := &ast.Ident{string(P.val), P.loc}; // Pos_を最後に
P.next();
return x;
}
// ...
return &ast.Ident{"", P.loc}; // Pos_を最後に
}
func (P *Parser) parseQualifiedIdent() ast.Expr {
// ...
var x ast.Expr = P.parseIdent();
for P.tok == token.PERIOD {
pos := P.loc; // locからposへ
P.next();
sel := P.parseIdent();
x = &ast.Selector{x, sel, pos}; // 新しいコンストラクタ
}
return x;
}
func (P *Parser) parseFunctionLit() ast.Expr {
// ...
pos := P.loc; // locからposへ
P.expect(token.FUNC);
typ := P.parseSignature();
P.expr_lev++;
body := P.parseBlock(token.LBRACE);
P.expr_lev--;
return &ast.FunctionLit{typ, body, pos}; // 新しいコンストラクタ
}
func (P *Parser) parseStringLit() ast.Expr {
// ...
list := vector.New(0);
for P.tok == token.STRING {
list.Push(&ast.BasicLit{token.STRING, P.val, P.loc}); // BasicLitのコンストラクタ変更
P.next();
}
// ...
return &ast.StringLit{strings}; // StringLitノードを返す
}
func (P *Parser) parseOperand() ast.Expr {
// ...
case token.INT, token.FLOAT, token.CHAR:
x := &ast.BasicLit{P.tok, P.val, P.loc}; // BasicLitのコンストラクタ変更
P.next();
return x;
// ...
case token.LPAREN:
lparen := P.loc; // locからlparenへ
P.next();
P.expr_lev++;
x := P.parseExpression(1);
P.expr_lev--;
rparen := P.expect(token.RPAREN); // expectがPositionを返す
return &ast.Group{x, lparen, rparen}; // 新しいコンストラクタ
// ...
}
func (P *Parser) parseSelectorOrTypeAssertion(x ast.Expr) ast.Expr { // 関数名変更
// ...
period := P.expect(token.PERIOD); // locからperiodへ
if P.tok == token.IDENT {
// selector
sel := P.parseIdent();
return &ast.Selector{x, sel, period}; // 新しいコンストラクタ
}
// type assertion
lparen := P.expect(token.LPAREN);
// ...
rparen := P.expect(token.RPAREN);
return &ast.TypeAssertion{x, typ, period, lparen, rparen}; // 新しいコンストラクタ
}
func (P *Parser) parseIndexOrSlice(x ast.Expr) ast.Expr { // 関数名変更
// ...
lbrack := P.expect(token.LBRACK); // locからlbrackへ
P.expr_lev++;
index := P.parseExpression(1);
P.expr_lev--;
if P.tok == token.RBRACK {
// index
rbrack := P.loc;
P.next();
return &ast.Index{x, index, lbrack, rbrack}; // 新しいコンストラクタ
}
// slice
colon := P.expect(token.COLON);
P.expr_lev++;
end := P.parseExpression(1);
P.expr_lev--;
rbrack := P.expect(token.RBRACK);
return &ast.Slice{x, index, end, lbrack, colon, rbrack}; // 新しいコンストラクタ
}
func (P *Parser) parseCall(fun ast.Expr) *ast.Call { // 新規関数
// ...
lparen := P.expect(token.LPAREN);
var args []ast.Expr;
if P.tok != token.RPAREN {
args = P.parseExpressionList();
}
rpapren := P.expect(token.RPAREN);
return &ast.Call{fun, args, lparen, rparen}; // 新しいコンストラクタ
}
func (P *Parser) parseCompositeLit(typ ast.Expr) ast.Expr { // 新規関数
// ...
lbrace := P.expect(token.LBRACE);
var elts []ast.Expr;
if P.tok != token.RBRACE {
elts = P.parseElementList();
}
rbrace := P.expect(token.RBRACE);
return &ast.CompositeLit{typ, elts, lbrace, rbrace}; // 新しいコンストラクタ
}
func (P *Parser) parseUnaryExpr() ast.Expr {
// ...
loc, tok := P.loc, P.tok;
P.next();
y := P.parseUnaryExpr();
return &ast.UnaryExpr{tok, y, loc}; // 新しいコンストラクタ
}
func (P *Parser) parseBinaryExpr(prec1 int) ast.Expr {
// ...
loc, tok := P.loc, P.tok;
P.next();
y := P.parseBinaryExpr(prec + 1);
x = &ast.BinaryExpr{tok, x, y, loc}; // 新しいコンストラクタ
// ...
}
// エラー報告の変更例
// 変更前: P.error(list.At(i).(ast.Expr).Loc(), "identifier expected");
// 変更後: P.error(list.At(i).(ast.Expr).Pos(), "identifier expected");
usr/gri/pretty/printer.go
ASTノードの出力ロジックの変更と、Pos()
メソッドの使用への移行が中心です。
// 変更前:
// func (P *Printer) HtmlIdentifier(x *ast.Ident) { P.String(x.Loc_, x.Str); }
// func (P *Printer) DoBinaryExpr(x *ast.BinaryExpr) { ... if x.Tok == token.COMMA { ... } else { ... P.Token(x.Loc_, x.Tok); ... } }
// func (P *Printer) DoUnaryExpr(x *ast.UnaryExpr) { ... P.Token(x.Loc_, x.Tok); ... }
// func (P *Printer) DoConcatExpr(x *ast.ConcatExpr) { ... }
// func (P *Printer) DoBasicLit(x *ast.BasicLit) { P.String(x.Loc_, string(x.Val)); }
// func (P *Printer) DoFunctionLit(x *ast.FunctionLit) { P.Token(x.Loc_, token.FUNC); ... }
// func (P *Printer) DoGroup(x *ast.Group) { P.Token(x.Loc_, token.LPAREN); ... }
// func (P *Printer) DoSelector(x *ast.Selector) { P.Token(x.Loc_, token.PERIOD); ... }
// func (P *Printer) DoTypeGuard(x *ast.TypeGuard) { P.Token(x.Loc_, token.PERIOD); ... }
// func (P *Printer) DoIndex(x *ast.Index) { P.Token(x.Loc_, token.LBRACK); ... }
// func (P *Printer) DoCall(x *ast.Call) { P.Token(x.Loc_, x.Tok); ... }
// func (P *Printer) DoChannelType(x *ast.ChannelType) { switch x.Mode { ... } }
// func (P *Printer) DoImportDecl(d *ast.ImportDecl) { ... P.String(d.Path.Loc(), ""); ... }
// 変更後:
func (P *Printer) HtmlIdentifier(x *ast.Ident) {
P.String(x.Pos_, x.Str); // Loc_からPos_へ
}
func (P *Printer) DoBinaryExpr(x *ast.BinaryExpr) {
// token.COMMAの特殊処理が削除され、OpとPos_を使用
prec := token.Precedence(x.Op);
// ...
P.Token(x.Pos_, x.Op); // Loc_からPos_へ、TokからOpへ
// ...
}
func (P *Printer) DoUnaryExpr(x *ast.UnaryExpr) {
// token.RANGEの特殊処理が削除され、OpとPos_を使用
// ...
P.Token(x.Pos_, x.Op); // Loc_からPos_へ、TokからOpへ
// ...
}
// func (P *Printer) DoConcatExpr(x *ast.ConcatExpr) は削除
func (P *Printer) DoBasicLit(x *ast.BasicLit) {
P.String(x.Pos_, string(x.Lit)); // Loc_からPos_へ、ValからLitへ
}
func (P *Printer) DoStringLit(x *ast.StringLit) { // 新規
for i, x := range x.Strings {
if i > 0 {
P.separator = blank;
}
P.DoBasicLit(x);
}
}
func (P *Printer) DoFunctionLit(x *ast.FunctionLit) {
P.Token(x.Func, token.FUNC); // Loc_からFuncへ
P.Signature(x.Typ);
P.separator = blank;
P.Block(x.Body, true);
}
func (P *Printer) DoGroup(x *ast.Group) {
P.Token(x.Lparen, token.LPAREN); // Loc_からLparenへ
P.Expr(x.X);
P.Token(x.Rparen, token.RPAREN); // nolocからRparenへ
}
func (P *Printer) DoSelector(x *ast.Selector) {
P.Expr1(x.X, token.HighestPrec);
P.Token(x.Period, token.PERIOD); // Loc_からPeriodへ
P.Expr1(x.Sel, token.HighestPrec);
}
func (P *Printer) DoTypeAssertion(x *ast.TypeAssertion) { // 関数名変更
P.Expr1(x.X, token.HighestPrec);
P.Token(x.Period, token.PERIOD); // Loc_からPeriodへ
P.Token(x.Lparen, token.LPAREN); // nolocからLparenへ
P.Expr(x.Typ);
P.Token(x.Rparen, token.RPAREN); // nolocからRparenへ
}
func (P *Printer) DoIndex(x *ast.Index) {
P.Expr1(x.X, token.HighestPrec);
P.Token(x.Lbrack, token.LBRACK); // Loc_からLbrackへ
P.Expr(x.Index); // IからIndexへ
P.Token(x.Rbrack, token.RBRACK); // nolocからRbrackへ
}
func (P *Printer) DoSlice(x *ast.Slice) { // 新規
P.Expr1(x.X, token.HighestPrec);
P.Token(x.Lbrack, token.LBRACK);
P.Expr(x.Beg);
P.Token(x.Colon, token.COLON);
P.Expr(x.End);
P.Token(x.Rbrack, token.RBRACK);
}
func (P *Printer) DoCall(x *ast.Call) {
P.Expr1(x.Fun, token.HighestPrec); // FからFunへ
P.Token(x.Lparen, token.LPAREN); // Loc_とTokからLparenへ
P.Exprs(x.Args); // ArgsをExprsで処理
P.Token(x.Rparen, token.RPAREN); // nolocからRparenへ
}
func (P *Printer) DoCompositeLit(x *ast.CompositeLit) { // 新規
P.Expr1(x.Typ, token.HighestPrec);
P.Token(x.Lbrace, token.LBRACE);
P.Exprs(x.Elts);
P.Token(x.Rbrace, token.RBRACE);
}
func (P *Printer) DoChannelType(x *ast.ChannelType) {
switch x.Dir { // ModeからDirへ
// ...
}
}
func (P *Printer) DoImportDecl(d *ast.ImportDecl) {
// ...
if d.Name != nil {
P.Expr(d.Name);
} else {
P.String(d.Path.Pos(), ""); // Loc()からPos()へ
}
// ...
if lit, is_lit := d.Path.(*ast.StringLit); is_lit { // BasicLitからStringLitへ
// ...
P.HtmlPackageName(lit.Pos(), string(lit.Strings[0].Lit)); // Loc_からPos_へ、ValからStrings[0].Litへ
}
// ...
}
コアとなるコードの解説
このコミットのコアとなる変更は、Go言語のASTがより表現豊かで、ソースコードの構造を正確に反映するように進化している点にあります。
-
Position
型への統一とPos()
メソッドの導入:- 以前は
scanner.Location
型が直接使われたり、ASTノードのフィールド名がLoc_
と不統一だったりしました。これをPosition
型に統一し、すべての式ノードがExpr
インターフェースのPos()
メソッドを通じて自身の開始位置を返すようにしたことで、ASTの各要素がソースコード上のどこに位置するかを、より一貫性のある方法で取得できるようになりました。これは、エラーメッセージの精度向上や、IDEなどの開発ツールがコードの特定部分にジャンプする機能の実装に不可欠です。
- 以前は
-
新しいASTノードの追加と既存ノードの再設計:
StringLit
: Go言語のユニークな機能である隣接する文字列リテラルの自動連結を、ASTレベルで明示的に表現できるようになりました。これにより、パーサーは複数の文字列リテラルを一つのStringLit
ノードとして扱い、プリティプリンターはそれを適切に整形できます。以前のConcatExpr
は、より汎用的な連結を表すものでしたが、文字列リテラルに特化したStringLit
の導入により、セマンティクスが明確になりました。CompositeLit
: 構造体、配列、スライス、マップの初期化構文は、Go言語の表現力を高める重要な要素です。CompositeLit
ノードの導入により、これらの複雑な初期化式がASTで正確に表現できるようになり、コンパイラが型チェックやコード生成を行う際の基盤が強化されました。Slice
とTypeAssertion
: これらはGo言語の基本的な操作であり、それぞれ専用のASTノードを持つことで、パーサーがこれらの構文をより効率的かつ正確に解析し、コンパイラの他のフェーズがその意味を容易に解釈できるようになります。TypeGuard
からTypeAssertion
への変更は、より適切な用語への移行を示しています。- ノードフィールドの追加:
FunctionLit
のFunc
、Group
のLparen
/Rparen
、Selector
のPeriod
など、各ノードが関連するキーワードや記号のPosition
を保持するようになったことは非常に重要です。これにより、ASTノードが単に構文構造だけでなく、ソースコードの具体的なレイアウト情報も保持するようになり、より忠実なコード表現が可能になります。これは、gofmt
のようなコードフォーマッターが、単にASTを再構築するだけでなく、元のコードの整形スタイルをある程度維持しながら整形を行う上で役立ちます。
-
ExprVisitor
インターフェースの拡張:- 新しいASTノードに対応する
Do
メソッドが追加されたことで、ASTを走査する際に、すべての式ノードに対して統一されたインターフェースで処理を記述できるようになりました。これは、コンパイラの意味解析フェーズや、静的解析ツール、コード生成器などがASTを効率的に処理するための基盤となります。Visitorパターンは、ASTの構造を変更することなく、新しい操作を追加できるため、コンパイラの拡張性を高めます。
- 新しいASTノードに対応する
-
パーサーとプリティプリンターの同期:
- ASTの変更は、パーサーとプリティプリンターに直接的な影響を与えます。このコミットでは、パーサーが新しいAST構造を正しく構築するようにロジックが調整され、プリティプリンターがそのASTを元に整形されたコードを正確に生成するように更新されました。特に、パーサーの関数がより細かく分割されたり、
expect
関数が位置情報を返すようになったりしたことは、パーサーの堅牢性とエラー報告の精度向上に寄与しています。プリティプリンター側では、ASTがより詳細な情報を持つようになったことで、以前必要だった特殊な整形ロジックが簡素化された箇所も見られます。
- ASTの変更は、パーサーとプリティプリンターに直接的な影響を与えます。このコミットでは、パーサーが新しいAST構造を正しく構築するようにロジックが調整され、プリティプリンターがそのASTを元に整形されたコードを正確に生成するように更新されました。特に、パーサーの関数がより細かく分割されたり、
これらの変更は、Go言語のコンパイラがより洗練され、将来の言語機能の追加や、より高度な開発ツールの実現に向けた強固な基盤を築く上で不可欠なものでした。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Go言語のASTパッケージ (
go/ast
): https://pkg.go.dev/go/ast - Go言語のScannerパッケージ (
go/scanner
): https://pkg.go.dev/go/scanner - Go言語のTokenパッケージ (
go/token
): https://pkg.go.dev/go/token - Go言語の仕様: https://go.dev/ref/spec
参考にした情報源リンク
- Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- 抽象構文木 (Wikipedia): https://ja.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E6%A7%8B%E6%96%87%E6%9C%A8
- Visitorパターン (Wikipedia): https://ja.wikipedia.org/wiki/Visitor_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
- Go言語のASTに関するブログ記事や解説(一般的な情報源として参照)
- (具体的なURLは検索結果によるため、ここでは一般的な説明に留めますが、実際の作業では「Go AST tutorial」や「Go parser implementation」などで検索しました。)
- Go言語のコンパイラ設計に関する論文やドキュメント(もしあれば)
- (初期のGoコンパイラに関する公開された詳細な設計ドキュメントは少ないため、主にコードベースの変更履歴から推測しました。)