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

[インデックス 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)の設計が非常に重要になります。

変更の主な背景は以下の通りです。

  1. ASTの整理と標準化: 初期段階のASTは、試行錯誤の中で設計されており、一貫性や明確さに欠ける部分がありました。このコミットは、ASTノードの命名規則、フィールドの構造、および位置情報の扱いを標準化し、より堅牢な基盤を構築することを目指しました。
  2. 言語機能の進化への対応: Go言語は当時も進化を続けており、新しい構文や表現が導入されていました。例えば、複合リテラル(CompositeLit)、スライス式(Slice)、型アサーション(TypeAssertion)などは、言語の表現力を高めるために必要不可欠な要素でした。これらの新しい構造を正確に表現できるASTノードの追加が求められました。
  3. パーサーとプリティプリンターの改善: ASTの変更に伴い、それを構築するパーサーと、ASTを元にコードを整形するプリティプリンターも更新する必要がありました。特に、パーサーは新しいAST構造を正しく生成し、プリティプリンターは整形されたコードを正確に出力できるように調整されました。
  4. コード品質の向上と保守性の確保: Loc_からPos_への変更や、ExprVisitorインターフェースの整理は、コードの可読性と保守性を向上させるためのものです。これにより、将来の機能追加やバグ修正が容易になります。

これらの変更は、Go言語のコンパイラがより安定し、将来の発展に対応できるような基盤を築く上で不可欠なステップでした。

前提知識の解説

このコミットの変更内容を理解するためには、以下の概念を把握しておく必要があります。

  1. 抽象構文木(Abstract Syntax Tree: AST):

    • プログラミング言語のソースコードを、その構文構造に基づいて抽象的に表現した木構造のデータ構造です。コンパイラのフロントエンドにおいて、字句解析(Lexical Analysis)と構文解析(Syntactic Analysis)の後に生成されます。
    • ASTは、ソースコードの意味を保持しつつ、コメントや空白などの構文上重要でない詳細を排除します。これにより、コンパイラの次の段階(意味解析、最適化、コード生成など)でコードを効率的に処理できるようになります。
    • Go言語のASTは、go/astパッケージで定義されており、Expr(式)、Stmt(文)、Decl(宣言)などのインターフェースと、それらを実装する具体的なノード(例: ast.Identast.BinaryExprast.FuncDecl)で構成されます。
  2. 字句解析器(Scanner/Lexer):

    • ソースコードを読み込み、意味のある最小単位である「トークン(Token)」のストリームに変換するコンポーネントです。例えば、var x = 10というコードは、var(キーワード)、x(識別子)、=(演算子)、10(リテラル)といったトークンに分割されます。
    • Go言語では、go/scannerパッケージがこの機能を提供し、scanner.Location(現在のコミットではscanner.Positionに相当)はソースコード内のトークンの位置情報(ファイル名、行番号、列番号など)を表します。
  3. 構文解析器(Parser):

    • 字句解析器が生成したトークンのストリームを受け取り、言語の文法規則に従ってASTを構築するコンポーネントです。パーサーは、トークンの並びが文法的に正しいかどうかを検証し、エラーがあれば報告します。
    • このコミットで変更されているusr/gri/pretty/parser.goは、Go言語の初期のパーサーの実装の一部です。
  4. プリティプリンター(Pretty Printer):

    • ASTを受け取り、整形されたソースコードを生成するコンポーネントです。コンパイラの一部として、またはコードフォーマッター(例: gofmt)として使用されます。
    • このコミットで変更されているusr/gri/pretty/printer.goは、Go言語の初期のプリティプリンターの実装の一部です。
  5. Visitorパターン:

    • オブジェクト構造の要素に対して実行される操作を、その構造から分離することを可能にするデザインパターンです。ASTのような複雑な木構造を走査し、各ノードに対して特定の処理を行う際に非常に有用です。
    • Go言語のASTでは、ast.Walk関数とast.Visitorインターフェースがこのパターンを実装しており、ASTの各ノードを訪問して処理を行うことができます。このコミットでは、ExprVisitorインターフェースがこの役割を担っています。

技術的詳細

このコミットにおける技術的な変更は多岐にわたりますが、主要なポイントは以下の通りです。

  1. 位置情報の統一と明確化 (Loc_からPos_へ):

    • 以前はASTノードのソースコード上の位置情報を示すフィールド名がLoc_でしたが、このコミットでPos_に統一されました。これは、scanner.LocationPositionという型エイリアスに置き換えられたことと合わせて、より「位置」を明確に指し示す意図があります。
    • ExprインターフェースのLoc()メソッドもPos()メソッドに変更され、すべての式ノードが統一された方法で位置情報を提供するようになりました。これにより、ASTを走査するツールやデバッガーが、コードのどの部分に対応するASTノードであるかを容易に特定できるようになります。
  2. 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ノードは、LparenRparenというPositionフィールドが追加され、括弧の位置を保持するようになりました。
      • SelectorIndexCallUnaryExprBinaryExprノードも同様に、関連するキーワードや記号の位置を保持するためのPositionフィールドが追加されました。これにより、ASTノードがソースコード上のより多くの情報を保持できるようになり、エラー報告やツール開発の精度が向上します。
  3. ExprVisitorインターフェースの更新:

    • 新しいASTノード(DoStringLit, DoCompositeLit, DoSlice, DoTypeAssertion)に対応するDoメソッドが追加されました。
    • 削除されたノード(DoConcatExpr, DoTypeGuard)に対応するメソッドは削除されました。
    • 既存のノード(DoBinaryExpr, DoUnaryExpr)のメソッドシグネチャが、新しいAST構造に合わせて調整されました。これにより、ASTを走査して特定の処理を行う際に、すべての式ノードに対して一貫した方法でアクセスできるようになります。
  4. パーサーロジックの調整:

    • parser.goでは、ASTノードの変更に合わせて、それらを構築するロジックが全面的に修正されました。例えば、parseStringLit関数は複数の文字列リテラルをStringLitノードとして正しく解析するように変更され、parseSelectorOrTypeGuardparseSelectorOrTypeAssertionに、parseIndexparseIndexOrSliceにそれぞれ改名され、新しいASTノードを生成するように更新されました。
    • parseCallOrCompositeLitのような汎用的な解析関数が、parseCallparseCompositeLitのように、より具体的な関数に分割されました。これは、コードのモジュール性と可読性を向上させるための良いプラクティスです。
    • エラー報告の際に、Loc()の代わりにPos()を使用するように変更されました。
  5. プリティプリンターの調整:

    • printer.goでは、新しいASTノードの出力ロジックが追加され、既存のノードの出力ロジックも変更されました。例えば、DoStringLitDoCompositeLitDoSliceDoTypeAssertionなどの新しいDoメソッドが実装されました。
    • DoBinaryExprDoUnaryExprでは、以前の特殊なケース(カンマ区切りのリストやrangeキーワード)の処理が削除され、より一般的なAST構造に対応するように簡素化されました。これは、ASTがより詳細な情報を保持するようになったため、プリティプリンター側で特殊なロジックを減らせるようになったことを示唆しています。
    • Loc_からPos_への変更に伴い、P.String(x.Loc_, ...)のような呼び出しがP.String(x.Pos_, ...)に変更されました。

これらの変更は、Go言語のコンパイラの基盤をより堅牢で拡張性の高いものにするための重要なステップであり、言語の進化とツールの開発を支える上で不可欠なものでした。

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

このコミットにおけるコアとなるコードの変更箇所は、主にusr/gri/pretty/ast.gousr/gri/pretty/parser.gousr/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がより表現豊かで、ソースコードの構造を正確に反映するように進化している点にあります。

  1. Position型への統一とPos()メソッドの導入:

    • 以前はscanner.Location型が直接使われたり、ASTノードのフィールド名がLoc_と不統一だったりしました。これをPosition型に統一し、すべての式ノードがExprインターフェースのPos()メソッドを通じて自身の開始位置を返すようにしたことで、ASTの各要素がソースコード上のどこに位置するかを、より一貫性のある方法で取得できるようになりました。これは、エラーメッセージの精度向上や、IDEなどの開発ツールがコードの特定部分にジャンプする機能の実装に不可欠です。
  2. 新しいASTノードの追加と既存ノードの再設計:

    • StringLit: Go言語のユニークな機能である隣接する文字列リテラルの自動連結を、ASTレベルで明示的に表現できるようになりました。これにより、パーサーは複数の文字列リテラルを一つのStringLitノードとして扱い、プリティプリンターはそれを適切に整形できます。以前のConcatExprは、より汎用的な連結を表すものでしたが、文字列リテラルに特化したStringLitの導入により、セマンティクスが明確になりました。
    • CompositeLit: 構造体、配列、スライス、マップの初期化構文は、Go言語の表現力を高める重要な要素です。CompositeLitノードの導入により、これらの複雑な初期化式がASTで正確に表現できるようになり、コンパイラが型チェックやコード生成を行う際の基盤が強化されました。
    • SliceTypeAssertion: これらはGo言語の基本的な操作であり、それぞれ専用のASTノードを持つことで、パーサーがこれらの構文をより効率的かつ正確に解析し、コンパイラの他のフェーズがその意味を容易に解釈できるようになります。TypeGuardからTypeAssertionへの変更は、より適切な用語への移行を示しています。
    • ノードフィールドの追加: FunctionLitFuncGroupLparen/RparenSelectorPeriodなど、各ノードが関連するキーワードや記号のPositionを保持するようになったことは非常に重要です。これにより、ASTノードが単に構文構造だけでなく、ソースコードの具体的なレイアウト情報も保持するようになり、より忠実なコード表現が可能になります。これは、gofmtのようなコードフォーマッターが、単にASTを再構築するだけでなく、元のコードの整形スタイルをある程度維持しながら整形を行う上で役立ちます。
  3. ExprVisitorインターフェースの拡張:

    • 新しいASTノードに対応するDoメソッドが追加されたことで、ASTを走査する際に、すべての式ノードに対して統一されたインターフェースで処理を記述できるようになりました。これは、コンパイラの意味解析フェーズや、静的解析ツール、コード生成器などがASTを効率的に処理するための基盤となります。Visitorパターンは、ASTの構造を変更することなく、新しい操作を追加できるため、コンパイラの拡張性を高めます。
  4. パーサーとプリティプリンターの同期:

    • ASTの変更は、パーサーとプリティプリンターに直接的な影響を与えます。このコミットでは、パーサーが新しいAST構造を正しく構築するようにロジックが調整され、プリティプリンターがそのASTを元に整形されたコードを正確に生成するように更新されました。特に、パーサーの関数がより細かく分割されたり、expect関数が位置情報を返すようになったりしたことは、パーサーの堅牢性とエラー報告の精度向上に寄与しています。プリティプリンター側では、ASTがより詳細な情報を持つようになったことで、以前必要だった特殊な整形ロジックが簡素化された箇所も見られます。

これらの変更は、Go言語のコンパイラがより洗練され、将来の言語機能の追加や、より高度な開発ツールの実現に向けた強固な基盤を築く上で不可欠なものでした。

関連リンク

参考にした情報源リンク