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

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

このコミットは、Go言語の初期のコンパイラ/パーサーにおける抽象構文木(AST)の構造をリファクタリングするものです。具体的には、AST.Exprノードから不要な型情報フィールドを削除し、その情報をAST.Objectノードに集約することで、ASTの設計をよりクリーンかつ一貫性のあるものにしています。また、AST.Type構造体にスコープ情報を追加し、型に関連するシンボル解決の柔軟性を向上させています。

コミット

commit ba556a881811ed6b619037783fe9f4b5dc3c142f
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Jan 12 17:44:10 2009 -0800

    - removed an unnecessary field from AST.Expr nodes
    
    R=r
    OCL=22601
    CL=22601

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

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

元コミット内容

- removed an unnecessary field from AST.Expr nodes

変更の背景

このコミットの背景には、Go言語のコンパイラ開発におけるAST設計の洗練があります。初期のAST設計では、Expr(式)ノードがその式の型情報を直接t *Typeフィールドとして保持していました。しかし、ASTの各ノードは、そのノードが表現するプログラム要素に関する情報を可能な限り効率的かつ一貫した方法で保持すべきです。

Exprノードが直接型を持つことは、以下のような問題を引き起こす可能性があります。

  1. 冗長性: 式の型情報は、その式が参照するシンボル(変数、関数など)の定義に含まれるObjectノードに既に存在する場合があるため、Exprノードに重複して持つことは冗長です。
  2. 一貫性の欠如: ASTの他の部分で型情報がObjectを通じて管理されている場合、Exprだけが異なる方法で型を持つと、AST全体のデータ構造の一貫性が損なわれます。
  3. 保守性の低下: 型情報の取得ロジックがExpr.tExpr.obj.typの2箇所に分散すると、コードの理解や変更が複雑になります。

このコミットは、これらの問題を解決し、ASTの設計をよりモジュール化され、保守しやすいものにするために行われました。型情報をObjectノードに集約することで、Exprノードは純粋に式の構造を表現することに専念し、型解決はObjectを通じて行われるようになります。また、Type構造体にscopeフィールドを追加することで、構造体やインターフェースなどの複合型が持つスコープ(フィールドやメソッドの定義)を直接型に関連付けられるようになり、型チェックやシンボル解決のプロセスがより自然になります。

前提知識の解説

このコミットを理解するためには、以下の概念に関する基本的な知識が必要です。

  1. 抽象構文木 (Abstract Syntax Tree, AST): コンパイラやインタプリタがソースコードを解析する際に生成する、プログラムの構造を木構造で表現したものです。各ノードは、変数宣言、式、文などのプログラム要素に対応します。ASTは、その後のセマンティック解析、最適化、コード生成の基盤となります。

  2. コンパイラのフロントエンド: ソースコードを解析し、ASTを構築する部分です。主に以下のフェーズから構成されます。

    • 字句解析 (Lexical Analysis): ソースコードをトークン(最小単位の単語)のストリームに変換します。
    • 構文解析 (Syntactic Analysis): トークンのストリームを文法規則に従って解析し、ASTを構築します。このプロセスで、parser.goのようなファイルが重要な役割を果たします。
    • セマンティック解析 (Semantic Analysis): ASTを走査し、型チェック、スコープ解決、名前解決などの意味的な検証を行います。このフェーズで、型情報やシンボルテーブルが活用されます。
  3. Go言語のAST構造 (初期): このコミットが対象としているGo言語の初期のASTでは、以下のような主要な構造体が存在していました。

    • Node: ASTのすべてのノードの基底となるインターフェースまたは構造体。
    • Expr: 式を表すノード。例えば、a + bのような算術式、foo()のような関数呼び出し、10のようなリテラルなどがこれに該当します。
    • Object: プログラム内の名前付きエンティティ(変数、関数、型など)を表すノード。シンボルテーブルのエントリのような役割を果たし、そのエンティティの名前、種類、そして型情報などを保持します。
    • Type: 型を表すノード。プリミティブ型(int, stringなど)、複合型(struct, array, mapなど)、関数型などを表現します。
  4. ScannerToken: 字句解析器(Scanner)はソースコードを読み込み、Token(トークン)を生成します。Scanner.TYPE, Scanner.IDENT, Scanner.FUNC, Scanner.INTなどは、それぞれ型、識別子、関数、整数リテラルといったトークンの種類を表す定数です。

  5. array.Array: Go言語の初期のコードベースで使われていた、動的配列のようなデータ構造。現在のGoのsliceとは異なります。

これらの知識を持つことで、コミットがASTのどの部分をどのように変更し、それがコンパイラのどのフェーズに影響を与えるのかを深く理解することができます。

技術的詳細

このコミットの技術的な核心は、ASTノード間の情報共有と責任の分離にあります。

AST.Exprからのt *Typeフィールドの削除

以前のAST.Expr構造体には、t *Typeというフィールドが存在し、式自体の型を直接保持していました。これは、例えばNewTypeExpr関数が型を表現するExprを作成する際に、その型をe.tに直接代入するような使われ方をしていました。

// 変更前 (ast.go)
export type Expr struct {
    Node;
    x, y *Expr;
    obj *Object;
    t *Type; // このフィールドが削除される
}

// 変更前 (NewTypeExpr)
export func NewTypeExpr(t *Type) *Expr {
    e := new(Expr);
    e.pos, e.tok, e.t = t.pos, Scanner.TYPE, t; // e.t に型を直接代入
    return e;
}

このコミットでは、このt *Typeフィールドを削除します。これにより、Exprノードは式の構造(二項演算の左右のオペランド、単項演算のオペランドなど)と、それが参照するObject(リテラル値、識別子など)に特化します。

型情報のAST.Objectへの集約

Exprから型情報が削除された代わりに、型情報はAST.Object構造体のtyp *Typeフィールドに集約されます。Objectは、プログラム内の名前付きエンティティ(変数、関数、型など)の定義を表すため、そのエンティティの型を保持するのが自然です。

この変更に伴い、NewTypeExpr関数やParseFunctionLit関数など、型を表現するExprを作成する箇所では、以下のように変更されます。

// 変更後 (ast.go)
export type Expr struct {
    Node;
    x, y *Expr;
    obj *Object; // t *Type は削除
}

// 変更後 (NewTypeExpr)
export func NewTypeExpr(typ *Type) *Expr {
    obj := NewObject(typ.pos, TYPE, ""); // 新しいObjectを作成
    obj.typ = typ; // Objectのtypフィールドに型を代入
    return NewLit(Scanner.TYPE, obj); // このObjectを持つExprを生成
}

NewLit関数も変更され、pos引数が削除され、e.posobj.posから取得されるようになります。これは、リテラルの位置情報がそのリテラルに対応するObjectから供給されるべきであるという設計思想を反映しています。

AST.Typeへのscope *Scopeフィールドの追加

AST.Type構造体には、新たにscope *Scopeフィールドが追加されます。

// 変更後 (ast.go)
export type Type struct {
    Node;
    key *Type;
    elt *Type;
    list *array.Array; end int;
    scope *Scope; // 新しく追加されたフィールド
}

このscopeフィールドは、構造体型(struct)のフィールドやインターフェース型(interface)のメソッドなど、複合型が自身の内部に持つ名前空間(スコープ)を表現するために使用されます。これにより、型定義とそれに付随するシンボル(フィールド名、メソッド名など)の解決がより効率的かつ正確に行えるようになります。例えば、myStruct.fieldのようなアクセスがあった場合、myStructの型情報から直接そのスコープにアクセスし、fieldというシンボルを解決できるようになります。

パーサーとプリンターの調整

これらのAST構造の変更に伴い、parser.goprinter.go内の関連するロジックも更新されています。

  • parser.go: ExprType関数やParseFunctionLit関数など、Expr.tに直接アクセスしていた箇所がx.obj.typにアクセスするように変更されています。また、ParseVarDeclParseVarDeclListがそれぞれParseVarParseVarListにリネームされ、より汎用的な変数解析の役割を反映しています。ParseSelectorOrTypeGuardでは、型ガード(例: x.(type))の表現方法が変更され、型情報がx.yNewTypeExprとして格納されるようになりました。
  • printer.go: Expr1関数内の型表現や関数リテラルの出力ロジックが、x.obj.typを使用するように変更されています。特に型ガードの出力ロジックは、新しいAST構造に合わせて調整されています。

これらの変更は、コンパイラのセマンティック解析フェーズにおける型チェックとシンボル解決の堅牢性を高め、ASTの設計原則をより厳密に適用するための重要なステップです。

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

このコミットにおけるコアとなるコードの変更は、主に以下の3つのファイルに集中しています。

  1. usr/gri/pretty/ast.go:

    • Expr構造体からt *Typeフィールドが削除されました。
    • NewLit関数のシグネチャが変更され、pos引数が削除され、e.posobj.posから設定されるようになりました。
    • Type構造体にscope *Scopeフィールドが追加されました。
    • NewTypeExpr関数が大幅に変更され、Expr.tに直接型を代入する代わりに、Objectを介して型を関連付けるようになりました。
    --- a/usr/gri/pretty/ast.go
    +++ b/usr/gri/pretty/ast.go
    @@ -170,9 +170,6 @@ export type Expr struct {
     	Node;
     	x, y *Expr;  // binary (x, y) and unary (y) expressions
     	obj *Object;
    -
    -	// TODO this one should go as well
    -	t *Type;  // type expressions, function literal types
     }
     
     
    @@ -198,9 +195,10 @@ export func NewExpr(pos, tok int, x, y *Expr) *Expr {
     }
     
     
    -export func NewLit(pos, tok int, obj *Object) *Expr {
    +// TODO probably don't need the tok parameter eventually
    +export func NewLit(tok int, obj *Object) *Expr {
     	e := new(Expr);
    -	e.pos, e.tok, e.obj = pos, tok, obj;
    +	e.pos, e.tok, e.obj = obj.pos, tok, obj;
     	return e;
     }
     
    @@ -302,6 +300,7 @@ export type Type struct {\n     	key *Type;  // receiver type or map key
     	elt *Type;  // array, map, channel or pointer element type, function result type
     	list *array.Array; end int;  // struct fields, interface methods, function parameters
    +	scope *Scope;  // struct fields, methods
     }
     
     
    @@ -340,10 +340,10 @@ func (t *Type) nfields() int {
     
     
     // requires complete Type.pos access
    -export func NewTypeExpr(t *Type) *Expr {
    -\te := new(Expr);\n-\te.pos, e.tok, e.t = t.pos, Scanner.TYPE, t;\n-\treturn e;\n    +export func NewTypeExpr(typ *Type) *Expr {
    +\tobj := NewObject(typ.pos, TYPE, "");
    +\tobj.typ = typ;
    +\treturn NewLit(Scanner.TYPE, obj);
     }
     
     
    
  2. usr/gri/pretty/parser.go:

    • ExprType関数でx.tへのアクセスがx.obj.typに変更されました。
    • NewLitのシグネチャ変更に伴い、NewLitの呼び出し箇所でpos引数が削除されました。
    • ParseIdentx.pos = P.posが追加され、位置情報の上書きが行われるようになりました。
    • ParseVarDeclParseVarDeclListがそれぞれParseVarParseVarListにリネームされ、関連する呼び出し箇所も更新されました。
    • ParseFunctionLitで関数型がx.tではなくval.typObjectのフィールド)に代入されるようになりました。
    • ParseSelectorOrTypeGuardで型ガードの表現がx.tからx.y = AST.NewTypeExpr(...)に変更されました。
    • ParseCompositeLitで複合リテラルの型がx.tからx.obj.typに設定されるようになりました。
    • ParseUnaryExprでポインタ型の要素型がy.tからy.obj.typに変更されました。
    --- a/usr/gri/pretty/parser.go
    +++ b/usr/gri/pretty/parser.go
    @@ -195,7 +195,7 @@ func (P *Parser) Declare(p *AST.Expr, kind int) {
     func ExprType(x *AST.Expr) *AST.Type {
     	var t *AST.Type;
     	if x.tok == Scanner.TYPE {
    -		t = x.t;
    +		t = x.obj.typ;
     	} else if x.tok == Scanner.IDENT {
     		// assume a type name
     		t = AST.NewType(x.pos, AST.TYPENAME);
    @@ -213,7 +213,7 @@ func (P *Parser) NoType(x *AST.Expr) *AST.Expr {
     	if x != nil && x.tok == Scanner.TYPE {
     		P.Error(x.pos, "expected expression, found type");
     		val := AST.NewObject(x.pos, AST.NONE, "0");
    -		x = AST.NewLit(x.pos, Scanner.INT, val);
    +		x = AST.NewLit(Scanner.INT, val);
     	}\n     	return x;
     }
    @@ -248,7 +248,8 @@ func (P *Parser) ParseIdent(scope *AST.Scope) *AST.Expr {
     		} else {
     			assert(obj.kind != AST.NONE);
     		}
    -		x = AST.NewLit(P.pos, Scanner.IDENT, obj);
    +		x = AST.NewLit(Scanner.IDENT, obj);
    +		x.pos = P.pos;  // override obj.pos (incorrect if object was looked up!)
     		if P.verbose {
     			P.PrintIndent();
     			print("Ident = \"", P.val, "\"\n");
    @@ -382,10 +383,10 @@ func (P *Parser) ParseChannelType() *AST.Type {
     }
     
     
    -// TODO: The code below (ParseVarDecl, ParseVarDeclList) is all too
    -// complicated. There must be a better way to do this.\n-func (P *Parser) ParseVarDecl(expect_ident bool) *AST.Type {
    +// TODO: The code below (ParseVar, ParseVarList) is all too
    +// complicated. There must be a better way to do this.
    +func (P *Parser) ParseVar(expect_ident bool) *AST.Type {
     	t := AST.BadType;
     	if expect_ident {
     		x := P.ParseIdent(nil);
    @@ -401,13 +402,13 @@ func (P *Parser) ParseVarDecl(expect_ident bool) *AST.Type {
     }
     
     
    -func (P *Parser) ParseVarDeclList(list *array.Array, ellipsis_ok bool) {
    -	P.Trace("VarDeclList");
    +func (P *Parser) ParseVarList(list *array.Array, ellipsis_ok bool) {
    +	P.Trace("VarList");
     
    -	// parse a list of types
    +	// assume a list of types
    +	// (a list of identifiers looks like a list of type names)
     	i0 := list.Len();
     	for {
    -		list.Push(P.ParseVarDecl(ellipsis_ok /* param list */ && i0 > 0));
    +		list.Push(P.ParseVar(ellipsis_ok /* param list */ && i0 > 0));
     		if P.tok == Scanner.COMMA {
     			P.Next();
     		} else {
    @@ -415,6 +416,7 @@ func (P *Parser) ParseVarDeclList(list *array.Array, ellipsis_ok bool) {
     		}
     	}
     
    +	// if we had a list of identifiers, it must be followed by a type
     	typ := P.TryType();
     	if typ == nil && P.tok == Scanner.ELLIPSIS {
     		typ = AST.NewType(P.pos, AST.ELLIPSIS);
    @@ -460,10 +460,10 @@ func (P *Parser) ParseParameterList(ellipsis_ok bool) *array.Array {
     	P.Trace("ParameterList");
     
     	list := array.New(0);
    -	P.ParseVarDeclList(list, ellipsis_ok);
    +	P.ParseVarList(list, ellipsis_ok);
     	for P.tok == Scanner.COMMA {
     		P.Next();
    -		P.ParseVarDeclList(list, ellipsis_ok);
    +		P.ParseVarList(list, ellipsis_ok);
     	}
     
     	P.Ecart();
    @@ -623,7 +623,7 @@ func (P *Parser) ParseStructType() *AST.Type {
     
     		t.list = array.New(0);
     		for P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {
    -			P.ParseVarDeclList(t.list, false);
    +			P.ParseVarList(t.list, false);
     			if P.tok == Scanner.STRING {
     				// ParseOperand takes care of string concatenation
     				t.list.Push(P.ParseOperand());
    @@ -758,9 +758,9 @@ func (P *Parser) ParseFunctionLit() *AST.Expr {
     	P.Trace("FunctionLit");
     
     	val := AST.NewObject(P.pos, AST.NONE, "");
    -	x := AST.NewLit(P.pos, Scanner.FUNC, val);
    +	x := AST.NewLit(Scanner.FUNC, val);
     	P.Expect(Scanner.FUNC);
    -	x.t = P.ParseFunctionType();
    +	val.typ = P.ParseFunctionType();
     	P.expr_lev++;
     	P.scope_lev++;
     	val.block, val.end = P.ParseBlock();
    @@ -813,7 +813,7 @@ func (P *Parser) ParseOperand() *AST.Expr {
     
     	case Scanner.INT, Scanner.FLOAT, Scanner.STRING:
     		val := AST.NewObject(P.pos, AST.NONE, P.val);
    -		x = AST.NewLit(P.pos, P.tok, val);
    +		x = AST.NewLit(P.tok, val);
     		P.Next();
     		if x.tok == Scanner.STRING {
     			// TODO should remember the list instead of
    @@ -852,7 +852,7 @@ func (P *Parser) ParseSelectorOrTypeGuard(x *AST.Expr) *AST.Expr {
     
     	} else {
     		P.Expect(Scanner.LPAREN);
    -		x.t = P.ParseType();
    +		x.y = AST.NewTypeExpr(P.ParseType());
     		P.Expect(Scanner.RPAREN);
     	}
     
    @@ -966,7 +966,8 @@ func (P *Parser) ParseCompositeLit(t *AST.Type) *AST.Expr {
     	P.Trace("CompositeLit");
     
     	x := P.NewExpr(P.pos, Scanner.LBRACE, nil, nil);
    -	x.t = t;
    +	x.obj = AST.NewObject(t.pos, AST.TYPE, "");
    +	x.obj.typ = t;
     	P.Expect(Scanner.LBRACE);
     	if P.tok != Scanner.RBRACE {
     		x.y = P.ParseCompositeElements();
    @@ -1022,7 +1023,7 @@ func (P *Parser) ParseUnaryExpr() *AST.Expr {
     	if tok == Scanner.MUL && y.tok == Scanner.TYPE {
     		// pointer type
     		t := AST.NewType(pos, AST.POINTER);
    -		t.elt = y.t;
    +		t.elt = y.obj.typ;
     		x = AST.NewTypeExpr(t);
     	} else {
     		x = P.NewExpr(pos, tok, nil, y);
    @@ -1480,7 +1481,7 @@ func (P *Parser) ParseImportSpec(pos int) *AST.Decl {
     	if P.tok == Scanner.STRING {
     		// TODO eventually the scanner should strip the quotes
     		val := AST.NewObject(P.pos, AST.NONE, P.val);
    -		d.val = AST.NewLit(P.pos, Scanner.STRING, val);
    +		d.val = AST.NewLit(Scanner.STRING, val);
     		P.Next();
     	} else {
     		P.Expect(Scanner.STRING);  // use Expect() error handling
    
  3. usr/gri/pretty/printer.go:

    • Expr1関数内で、Scanner.TYPEScanner.FUNCScanner.LBRACE(複合リテラル)のケースで、型情報へのアクセスがx.tからx.obj.typに変更されました。
    • Scanner.SELECT(セレクタまたは型ガード)のケースで、型ガードの出力ロジックがx.y.tok == Scanner.TYPEをチェックするように変更され、P.Expr(x.y)で型式を出力するようになりました。
    --- a/usr/gri/pretty/printer.go
    +++ b/gri/pretty/printer.go
    @@ -547,7 +547,7 @@ func (P *Printer) Expr1(x *AST.Expr, prec1 int) {
     	switch x.tok {\n     	case Scanner.TYPE:
     		// type expr
    -		P.Type(x.t);
    +		P.Type(x.obj.typ);
     
     	case Scanner.IDENT:
     		P.HtmlIdentifier(x);
    @@ -559,7 +559,7 @@ func (P *Printer) Expr1(x *AST.Expr, prec1 int) {
     	case Scanner.FUNC:
     		// function literal
     		P.String(x.pos, "func");
    -		P.Type(x.t);
    +		P.Type(x.obj.typ);
     		P.Block(0, x.obj.block, x.obj.end, true);
     		P.newlines = 0;
     
    @@ -576,12 +576,12 @@ func (P *Printer) Expr1(x *AST.Expr, prec1 int) {
     		// selector or type guard
     		P.Expr1(x.x, Scanner.HighestPrec);
     		P.String(x.pos, ".");
    -		if x.y != nil {
    -			P.Expr1(x.y, Scanner.HighestPrec);
    -		} else {
    +		if x.y.tok == Scanner.TYPE {
     			P.String(0, "(");
    -			P.Type(x.t);
    +			P.Expr(x.y);
     			P.String(0, ")");
    +		} else {
    +			P.Expr1(x.y, Scanner.HighestPrec);
     		}
     		
     	case Scanner.LBRACK:
    @@ -599,8 +599,8 @@ func (P *Printer) Expr1(x *AST.Expr, prec1 int) {
     		P.String(0, ")");
     
     	case Scanner.LBRACE:
    -		// composite
    -		P.Type(x.t);
    +		// composite literal
    +		P.Type(x.obj.typ);
     		P.String(x.pos, "{");
     		P.Expr(x.y);
     		P.String(0, "}");
    

コアとなるコードの解説

このコミットの核心は、ASTのExprノードが直接型情報を持つのではなく、Objectノードを介して型情報を参照するように変更された点にあります。

  1. AST.Exprの簡素化: Expr構造体からt *Typeフィールドが削除されたことで、Exprノードは式の構造(例: x + yxy)と、それが参照する具体的な値やシンボル(obj *Object)に特化するようになりました。これにより、ASTの各ノードの責任が明確になり、設計がよりモジュール化されます。

  2. AST.Objectへの型情報の集約: Objectは、プログラム内の名前付きエンティティ(変数、関数、型など)の定義を表します。これらのエンティティは本質的に型を持つため、Object構造体にtyp *Typeフィールドを設けることで、型情報を一元的に管理できるようになります。 例えば、NewTypeExpr関数では、型を表す式を生成する際に、その型を直接Exprに埋め込むのではなく、新しく作成したObjecttypフィールドに設定し、そのObjectExprに紐付けます。これにより、型情報は常にObjectを通じてアクセスされるようになります。

    // ast.go の NewTypeExpr 関数 (変更後)
    export func NewTypeExpr(typ *Type) *Expr {
        obj := NewObject(typ.pos, TYPE, ""); // 型に対応する新しいObjectを作成
        obj.typ = typ; // Objectのtypフィールドに型を設定
        return NewLit(Scanner.TYPE, obj); // このObjectを持つExprを生成
    }
    

    このパターンは、関数リテラル(ParseFunctionLit)や複合リテラル(ParseCompositeLit)の型設定にも適用され、一貫性が保たれています。

  3. AST.Typeへのスコープ情報の追加: Type構造体にscope *Scopeフィールドが追加されたことは、特に複合型(構造体やインターフェース)のセマンティック解析において重要です。構造体はフィールドを持ち、インターフェースはメソッドを持ちますが、これらはそれぞれ独自のスコープを形成します。Typeノードが直接そのスコープへの参照を持つことで、型チェックや名前解決の際に、関連するシンボルを効率的に検索できるようになります。これは、コンパイラが型定義を処理する際の柔軟性と正確性を向上させます。

  4. パーサーとプリンターの適応: AST構造の変更は、ASTを構築するパーサーと、ASTを人間が読める形式に変換するプリンターに直接影響を与えます。

    • パーサー: Expr.tへの直接アクセスをExpr.obj.typへのアクセスに置き換えることで、新しいAST設計に準拠します。また、NewLitのシグネチャ変更に合わせて、リテラル生成の呼び出しも更新されます。ParseIdentにおけるx.pos = P.posの追加は、識別子の位置情報が常にパーサーの現在の位置と一致するようにするための細かな調整であり、デバッグやエラー報告の精度向上に寄与します。
    • プリンター: ASTの変更を反映し、型情報や関数リテラル、複合リテラルの出力時にExpr.obj.typを使用するように変更されます。特に型ガードの出力ロジックの変更は、AST上での型ガードの表現方法が変わったこと(x.tからx.yが型式を持つ形へ)に直接対応しています。

これらの変更は、Go言語コンパイラのASTが、より堅牢で、拡張性があり、かつセマンティック解析のニーズに合致するように進化していく過程を示しています。型情報をObjectに集約し、Typeにスコープ情報を追加することで、コンパイラはより複雑な型システムやシンボル解決の要件に対応できるようになります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • コンパイラ設計に関する一般的な書籍やオンラインリソース(AST、セマンティック解析、シンボルテーブルなど)
  • Gitのコミットログと差分表示
  • Go言語のソースコード(特にgo/astgo/parsergo/printerパッケージの初期バージョンに関する情報)
  • Go言語のIssueトラッカーやデザインドキュメント(もしこのコミットに関連する議論があれば)
  • Go言語の初期のコミットメッセージとコードコメント