[インデックス 1887] ファイルの概要
このコミットは、Go言語の初期開発段階における抽象構文木(AST)の構造変更に対応するため、パーサー(parser.go
)とプリンター(printer.go
)のコードベースを調整するものです。主にASTノードの命名規則の統一、リテラル表現の具体化、および関数シグネチャの表現方法の簡素化が行われています。
コミット
commit ba620d502751c6559dddd1ec125efac10c565a67
Author: Robert Griesemer <gri@golang.org>
Date: Wed Mar 25 12:45:06 2009 -0700
adjustments matching updated ast
R=r
OCL=26746
CL=26746
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ba620d502751c6559dddd1ec125efac10c565a67
元コミット内容
adjustments matching updated ast
R=r
OCL=26746
CL=26746
変更の背景
このコミットは、Go言語のコンパイラおよびツールチェインの中核をなすgo/ast
パッケージにおける抽象構文木(AST)の定義が更新されたことに伴うものです。Go言語の初期段階では、言語仕様や内部実装が頻繁に変更されていました。この「updated ast」は、ASTの設計がより堅牢で、表現力豊か、かつ一貫性のあるものになるように見直された結果と考えられます。
具体的な変更の背景としては、以下の点が挙げられます。
- 命名規則の統一: 以前は
Stat
(Statement)という略語が使われていましたが、より明示的なStmt
(Statement)への変更が行われました。同様に、Block
がBlockStmt
となることで、ブロックが文の一種であることが明確になります。これにより、コードの可読性と一貫性が向上します。 - リテラル表現の具体化:
ast.BasicLit
という汎用的な基本リテラル型から、ast.IntLit
、ast.FloatLit
、ast.CharLit
、ast.StringLit
といった具体的な型に分割されました。これにより、ASTノードが保持する情報のセマンティクスがより明確になり、コンパイラが型チェックやコード生成を行う際に、より正確な情報を利用できるようになります。また、文字列リテラルの連結(例:"hello" "world"
)をASTで表現するためのast.StringList
の導入も行われています。 - 関数シグネチャの簡素化:
ast.Signature
という構造体が廃止され、ast.FunctionType
が直接パラメータと結果のフィールドリストを持つように変更されました。これにより、関数型とシグネチャの表現が統合され、ASTの構造が簡素化されます。
これらの変更は、Go言語の進化の過程で、より洗練されたコンパイラインフラストラクチャを構築するための基盤固めの一環として行われたものです。
前提知識の解説
このコミットを理解するためには、以下の概念についての前提知識が必要です。
- 抽象構文木 (Abstract Syntax Tree, AST): プログラミング言語のソースコードを、その構文構造に基づいて抽象的に表現した木構造のデータ構造です。コンパイラのフロントエンド(字句解析、構文解析)によって生成され、その後のセマンティック解析、最適化、コード生成の段階で利用されます。ASTは、ソースコードの意味をコンピュータが理解しやすい形式で表現します。
- パーサー (Parser): ソースコードを読み込み、その構文規則に従ってASTを構築するプログラムの一部です。字句解析器(Lexer/Scanner)が生成したトークン列を入力として受け取ります。
- プリンター (Printer): ASTを読み込み、それを人間が読める形式のソースコード(または他の表現形式)に変換して出力するプログラムの一部です。Go言語の
go/printer
パッケージは、ASTからGoのソースコードを整形して出力する機能を提供します。 - Go言語の
go/ast
パッケージ: Go言語の標準ライブラリの一部であり、GoプログラムのASTを定義する型と関数を提供します。このパッケージの型は、Goのソースコードの各要素(宣言、文、式など)に対応するASTノードを表します。 - Go言語の
go/token
パッケージ: Go言語の標準ライブラリの一部であり、Goの字句要素(トークン、例:IDENT
、INT
、STRING
、LBRACE
など)を定義します。 vector.Vector
: Go言語の初期のコードベースで使われていた、動的配列のようなデータ構造です。現在のGoではスライス([]T
)が使われるのが一般的ですが、このコミット時点ではvector
パッケージが利用されていました。
技術的詳細
このコミットにおける技術的な変更は、主にusr/gri/pretty/parser.go
とusr/gri/pretty/printer.go
の2つのファイルに集中しています。これらはGo言語の初期のパーサーとプリンターの実装の一部です。
parser.go
の変更点
-
Stat
からStmt
へのリネーム:parseStatement()
関数の戻り値の型がast.Stat
からast.Stmt
に変更されました。asStatList()
関数がasStmtList()
にリネームされ、[]ast.Stat
から[]ast.Stmt
を返すようになりました。parseStatementList()
関数の戻り値の型も[]ast.Stat
から[]ast.Stmt
に変更されました。ast.LabeledStat
、ast.ExprStat
、ast.IncDecStat
、ast.AssignmentStat
、ast.GoStat
、ast.DeferStat
、ast.ReturnStat
、ast.ControlFlowStat
、ast.IfStat
、ast.SwitchStat
、ast.TypeSwitchStat
、ast.SelectStat
、ast.ForStat
、ast.RangeStat
、ast.DeclStat
、ast.EmptyStat
、ast.BadStat
といったASTノードが、それぞれast.LabeledStmt
、ast.ExprStmt
、ast.IncDecStmt
、ast.AssignStmt
、ast.GoStmt
、ast.DeferStmt
、ast.ReturnStmt
、ast.BranchStmt
、ast.IfStmt
、ast.SwitchStmt
、ast.TypeSwitchStmt
、ast.SelectStmt
、ast.ForStmt
、ast.RangeStmt
、ast.DeclStmt
、ast.EmptyStmt
、ast.BadStmt
にリネームされました。- これに伴い、関連するパーシング関数(例:
parseSimpleStat
->parseSimpleStmt
)もリネームされ、戻り値の型が変更されています。 ast.CompositeStat
は削除され、parseBlock
が直接*ast.BlockStmt
を返すようになりました。
-
リテラル型の具体化:
parseOperand()
関数内で、token.INT
、token.FLOAT
、token.CHAR
、token.STRING
に対応するASTノードが、ast.BasicLit
からそれぞれast.IntLit
、ast.FloatLit
、ast.CharLit
、ast.StringLit
に具体化されました。parseStringLit()
関数がparseStringList()
にリネームされ、*ast.StringLit
ではなく[]*ast.StringLit
を返すようになりました。これは、複数の隣接する文字列リテラルが単一のast.StringList
ノードとして扱われるようになったことを示唆しています。- 構造体のフィールドタグ(
tag
)も、以前はast.Expr
型でしたが、[]*ast.StringLit
型に変更され、parseStringList(nil)
でパースされるようになりました。
-
関数シグネチャの変更:
parseSignature()
関数が*ast.Signature
を返す代わりに、params []*ast.Field, results []*ast.Field
という複数の戻り値を返すようになりました。ast.FunctionType
の定義が変更され、Sig
フィールド(*ast.Signature
型)が削除され、代わりにParams
とResults
フィールド(それぞれ[]*ast.Field
型)が直接追加されました。これにより、関数シグネチャの表現がより直接的になりました。parseFunctionLit()
関数も、parseSignature()
の変更に合わせてparseFunctionType()
を呼び出すように修正されました。parseFuncDecl()
関数も、ast.FuncDecl
の構造変更(Sig
フィールドの削除とType
フィールドの追加)に合わせて調整されました。
-
parseBlock
からparseBlockStmt
への変更:parseBlock
関数がparseBlockStmt
にリネームされ、*ast.Block
を返す代わりに*ast.BlockStmt
を返すようになりました。これは、ブロックが文の一種であることを明確にするための変更です。
-
parseForStat
の変更:for
文のrange
節のパースロジックが更新され、ast.RangeStat
がast.RangeStmt
にリネームされ、その構造も変更されました。ast.AssignStmt
のLhs
とRhs
の要素数チェックがより厳密になりました。
printer.go
の変更点
printer.go
の変更は、parser.go
におけるAST構造の変更を反映したものです。
-
Stat
からStmt
へのリネーム:P.Stat(s)
の呼び出しがP.Stmt(s)
に変更されました。DoBadStat
、DoDeclStat
、DoEmptyStat
、DoLabeledStat
、DoExprStat
、DoIncDecStat
、DoAssignmentStat
、DoGoStat
、DoDeferStat
、DoReturnStat
、DoControlFlowStat
、DoIfStat
、DoSwitchStat
、DoTypeSwitchStat
、DoSelectStat
、DoForStat
、DoRangeStat
といったメソッドが、それぞれDoBadStmt
、DoDeclStmt
、DoEmptyStmt
、DoLabeledStmt
、DoExprStmt
、DoIncDecStmt
、DoAssignStmt
、DoGoStmt
、DoDeferStmt
、DoReturnStmt
、DoBranchStmt
、DoIfStmt
、DoSwitchStmt
、DoTypeSwitchStmt
、DoSelectStmt
、DoForStmt
、DoRangeStmt
にリネームされました。StatementList
関数も[]ast.Stat
から[]ast.Stmt
を受け取るように変更されました。
-
リテラル型の具体化の反映:
DoBasicLit
メソッドが削除され、代わりにDoIntLit
、DoFloatLit
、DoCharLit
、DoStringLit
が追加されました。DoStringLit
メソッドがast.StringLit
を受け取るように変更され、DoStringList
メソッドがast.StringList
を受け取り、複数の文字列リテラルを処理するように追加されました。
-
関数シグネチャの変更の反映:
Signature
関数が*ast.Signature
を受け取る代わりに、params, result []*ast.Field
という複数の引数を受け取るように変更されました。DoFunctionType
メソッドがx.Sig
ではなくx.Params, x.Results
を直接利用するように変更されました。DoFunctionLit
メソッドもx.Typ
ではなくx.Type
を利用し、P.Block(x.Body, true)
がP.Stmt(x.Body)
に変更されました。DoFuncDecl
メソッドもd.Sig
ではなくd.Type.Params, d.Type.Results
を利用するように変更されました。
-
Block
からBlockStmt
への変更の反映:P.Block(b, true)
の呼び出しがP.Stmt(b)
に変更されました。DoCompositeStat
メソッドが削除され、DoBlockStmt
メソッドが追加されました。
これらの変更は、ASTの構造がよりセマンティックに正確になり、パーサーとプリンターがその新しい構造を正しく処理できるようにするための、大規模なコードベースのリファクタリングを示しています。
コアとなるコードの変更箇所
usr/gri/pretty/parser.go
parseStatement()
の戻り値の型をast.Stat
からast.Stmt
に変更。parseResult()
内でresult
変数をresults
にリネームし、型を[]*ast.Field
に変更。parseSignature()
の戻り値を*ast.Signature
からparams []*ast.Field, results []*ast.Field
に変更。parseFunctionType()
でast.FunctionType
の初期化を{pos, sig}
から{pos, params, results}
に変更。parseMethodSpec()
でast.FunctionType
の初期化を同様に変更。parseStringLit()
をparseStringList()
にリネームし、戻り値を*ast.StringLit
から[]*ast.StringLit
に変更。parseFieldDecl()
のtag
変数の型をast.Expr
から[]*ast.StringLit
に変更。asStatList()
をasStmtList()
にリネームし、[]ast.Stat
から[]ast.Stmt
を返すように変更。parseStatementList()
の戻り値の型を[]ast.Stat
から[]ast.Stmt
に変更。parseBlock()
をparseBlockStmt()
にリネームし、戻り値を*ast.Block
から*ast.BlockStmt
に変更。parseFunctionLit()
でast.FunctionLit
の初期化を{pos, typ, body}
から{typ, body}
に変更。parseOperand()
でtoken.INT
,token.FLOAT
,token.CHAR
,token.STRING
のリテラルをそれぞれast.IntLit
,ast.FloatLit
,ast.CharLit
,ast.StringLit
としてパースするように変更。token.STRING
の場合はast.StringList
も考慮。parseSimpleStat()
をparseSimpleStmt()
にリネームし、ast.LabeledStat
、ast.AssignmentStat
、ast.IncDecStat
、ast.ExprStat
をそれぞれast.LabeledStmt
、ast.AssignStmt
、ast.IncDecStmt
、ast.ExprStmt
に置き換え。parseGoStat()
をparseGoStmt()
に、parseDeferStat()
をparseDeferStmt()
に、parseReturnStat()
をparseReturnStmt()
に、parseControlFlowStat()
をparseBranchStmt()
にリネームし、対応するASTノードも変更。parseIfStat()
をparseIfStmt()
に、parseSwitchStat()
をparseSwitchStmt()
に、parseSelectStat()
をparseSelectStmt()
に、parseForStat()
をparseForStmt()
にリネームし、対応するASTノードも変更。parseCaseClause()
、parseTypeCaseClause()
、parseCommClause()
でP.parseBlock(token.COLON)
の代わりにcolon := P.expect(token.COLON); body := P.parseStatementList()
を使用するように変更。parseStatement()
内のswitch
文で、ast.DeclStat
、ast.CompositeStat
、ast.EmptyStat
、ast.BadStat
をそれぞれast.DeclStmt
、ast.BlockStmt
、ast.EmptyStmt
、ast.BadStmt
に置き換え。parseImportSpec()
でpath
の型を*ast.StringLit
から[]*ast.StringLit
に変更。parseFunctionDecl()
でast.FuncDecl
の初期化を{doc, pos, recv, ident, sig, body}
から{doc, recv, ident, &ast.FunctionType{pos, params, results}, body}
に変更。
usr/gri/pretty/printer.go
Parameters()
内でpar.Typ
をpar.Type
に変更。Signature()
関数がsig *ast.Signature
を受け取る代わりにparams, result []*ast.Field
を受け取るように変更。Fields()
内でfld.Typ
をfld.Type
に変更し、fld.Tag
を&ast.StringList{fld.Tag}
として出力するように変更。Block()
関数をコメントアウトし、Stmt(s ast.Stmt)
関数を追加。DoBasicLit()
を削除し、DoIntLit()
、DoFloatLit()
、DoCharLit()
、DoStringLit()
、DoStringList()
を追加。DoFunctionLit()
でx.Typ
をx.Type
に変更し、P.Block(x.Body, true)
をP.Stmt(x.Body)
に変更。DoTypeAssertExpr()
でx.Typ
をx.Type
に変更。DoCompositeLit()
でx.Typ
をx.Type
に変更。DoFunctionType()
でx.Sig
をx.Params, x.Results
に変更。Stat(s ast.Stat)
をStmt(s ast.Stmt)
にリネーム。DoBadStat()
をDoBadStmt()
に、DoDeclStat()
をDoDeclStmt()
に、DoEmptyStat()
をDoEmptyStmt()
に、DoLabeledStat()
をDoLabeledStmt()
に、DoExprStat()
をDoExprStmt()
に、DoIncDecStat()
をDoIncDecStmt()
に、DoAssignmentStat()
をDoAssignStmt()
に、DoGoStat()
をDoGoStmt()
に、DoDeferStat()
をDoDeferStmt()
に、DoReturnStat()
をDoReturnStmt()
に、DoControlFlowStat()
をDoBranchStmt()
にリネーム。StatementList()
が[]ast.Stat
から[]ast.Stmt
を受け取るように変更。DoCompositeStat()
を削除し、DoBlockStmt()
を追加。ControlClause()
内でP.Stat(init)
をP.Stmt(init)
に、P.Stat(post)
をP.Stmt(post)
に変更。DoIfStat()
をDoIfStmt()
に、DoSwitchStat()
をDoSwitchStmt()
に、DoTypeSwitchStat()
をDoTypeSwitchStmt()
に、DoSelectStat()
をDoSelectStmt()
に、DoForStat()
をDoForStmt()
に、DoRangeStat()
をDoRangeStmt()
にリネームし、対応するASTノードも変更。DoCaseClause()
、DoTypeCaseClause()
、DoCommClause()
でP.Block(s.Body, true)
の代わりにP.Token(s.Colon, token.COLON); P.indentation++; P.StatementList(s.Body); P.indentation--;
を使用するように変更。DoImportDecl()
でd.Path.Pos()
をd.Path[0].Pos()
に、d.Path.Strings
をd.Path
に、d.Path.Strings[0].Lit
をd.Path[0].Lit
に変更。DoConstDecl()
、DoTypeDecl()
、DoVarDecl()
でd.Typ
をd.Type
に変更。DoFuncDecl()
でd.Func
をd.Type.Func
に、d.Recv.Typ
をd.Recv.Type
に、d.Sig
をd.Type.Params, d.Type.Results
に変更。Interface()
でd.Recv.Typ
をd.Recv.Type
に変更。
コアとなるコードの解説
このコミットの核心は、Go言語のASTがより厳密で、セマンティックに豊かな表現を持つように再設計されたことにあります。
Stat
からStmt
への変更
これは、Statement
の略語としてStat
ではなくStmt
を使用するという、命名規則の統一です。Go言語の設計哲学では、明確さと一貫性が重視されます。この変更は、ASTノードの命名においてもその原則を適用し、コードベース全体の可読性を向上させることを目的としています。例えば、ast.IfStat
がast.IfStmt
になることで、それが「If文」を表すASTノードであることがより直感的に理解できます。
リテラル型の具体化
以前は、整数、浮動小数点数、文字、文字列といった異なる種類のリテラルがすべてast.BasicLit
という単一の汎用型で表現されていました。このコミットでは、これらをast.IntLit
、ast.FloatLit
、ast.CharLit
、ast.StringLit
といった具体的な型に分割しています。
この変更の利点は以下の通りです。
- 型安全性とセマンティクスの明確化: 各リテラルが自身の型を持つことで、ASTを処理するコンパイラの各段階(型チェック、最適化など)で、より正確な型情報に基づいて処理を行うことができます。例えば、整数リテラルと浮動小数点数リテラルは異なる内部表現や演算規則を持つため、ASTレベルで区別することで、これらの処理をより効率的かつ安全に行えます。
- コードの簡素化:
ast.BasicLit
を使用する場合、そのKind
フィールド(token.INT
、token.FLOAT
など)をチェックしてリテラルの種類を判別する必要がありました。具体的な型に分割することで、型アサーションや型スイッチを使って直接リテラルの種類を判別できるようになり、コードが簡素化されます。 - 文字列リテラルの連結:
parseStringList
の導入とast.StringList
の利用は、Go言語が複数の隣接する文字列リテラルを自動的に連結する機能(例:"hello" + " " + "world"
ではなく"hello" " " "world"
)をサポートするためのAST表現の変更です。これにより、パーサーはこれらのリテラルを単一の論理的な文字列として扱うことができます。
関数シグネチャの簡素化
以前は、関数型(ast.FunctionType
)がast.Signature
という別の構造体を介してパラメータと結果の情報を保持していました。このコミットでは、ast.Signature
を廃止し、ast.FunctionType
が直接Params
とResults
のフィールドを持つように変更されました。
この変更の利点は以下の通りです。
- ASTのフラット化: 不要な中間層(
ast.Signature
)がなくなることで、ASTの構造がよりフラットになり、ナビゲーションや処理が簡素化されます。 - 表現の一貫性: 関数型自体がそのシグネチャのすべての情報を持つことで、ASTの表現が一貫性を持ちます。
これらの変更は、Go言語のコンパイラがより効率的で、堅牢で、保守しやすいものになるように、ASTの基盤を強化する重要なステップでした。
関連リンク
- Go言語のASTに関する公式ドキュメント(現在のバージョン): https://pkg.go.dev/go/ast
- Go言語のトークンに関する公式ドキュメント(現在のバージョン): https://pkg.go.dev/go/token
参考にした情報源リンク
- Go言語のソースコード(GitHubリポジトリ): https://github.com/golang/go
- Go言語の初期のコミット履歴(GitHub): https://github.com/golang/go/commits/master?after=ba620d502751c6559dddd1ec125efac10c565a67+34&branch=master
- Go言語の設計に関する議論やドキュメント(Go Wikiなど、当時の情報源は特定が困難な場合があるため、一般的な情報源を記載)
- Go Wiki: https://go.dev/wiki
- The Go Programming Language Specification: https://go.dev/ref/spec
- Effective Go: https://go.dev/doc/effective_go
- Go Blog: https://go.dev/blog/
- Go Language Design Documents (一部は公開されているが、このコミット時点の具体的な設計文書は少ない可能性): https://go.dev/design/