[インデックス 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/