Goの抽象構文木（AST）を手入力してHello, Worldを作る #golang

## はじめに

タイトルを見て、「はて？何を言ってるんだろう」と思った方もいるでしょう。
その通りです。通常、抽象構文木（AST)を取得するには、「[ASTを取得する方法を調べる](http://qiita.com/tenntenn/items/13340f2845316532b55a)」で解説したように、[`go/parser`](https://golang.org/pkg/go/parser/)パッケージの関数を使ってソースコードをパースする必要があります。

しかし、この記事では温かみのある手入力をすることで、日頃なんとなく取得しているASTがどういうノードで構築されているのか、最低限必要なフィールドは何なのかということを改めて知ることを目的としています。

なお、この記事を書いた時のGoの最新バージョンは1.7.4です。

## 今回作るコード

今回はかのプログラム言語Cやプログラミング言語Goで有名な`Hello, World`を出力するプログラムを作りたいと思います。

具体的には、以下のようなコードです。
なお、せっかくなので、ここではGoっぽく、`Hello, 世界`としています。

```go
package main

import "fmt"

func main() {
	fmt.Println("Hello, 世界")
}
```

## `ast.File`を作る

この記事では、1つのファイルから構成されるコードを作るので、[`ast.File`](http://golang.org/pkg/go/ast/#File)構造体を作っていきます。

`ast.File`構造体は以下のようなフィールドを持ちます。

```go
type File struct {
        Doc        *CommentGroup   // associated documentation; or nil
        Package    token.Pos       // position of "package" keyword
        Name       *Ident          // package name
        Decls      []Decl          // top-level declarations; or nil
        Scope      *Scope          // package scope (this file only)
        Imports    []*ImportSpec   // imports in this file
        Unresolved []*Ident        // unresolved identifiers in this file
        Comments   []*CommentGroup // list of all comments in the source file
}
```

この中で今回作成するコードに必須なものはどれでしょうか？
`Name`フィールドはいりそうですね。
あとは、`Decls`フィールドは、パッケージのインポートとか関数の定義とかで必要になりそうです。
`token.Pos`方のフィールドは、きっとフォーマッターがよしなに位置を決めてくれるから無視しても良さそうです。

## インポートする

`fmt`パッケージをインポートする部分をASTで書いてみましょう。
インポートは、[`ast.GenDecl`](https://golang.org/pkg/go/ast/#GenDecl)構造体で表すことができます。
`ast.GenDecl`構造体は、`import`や`var`、`const`、`type`などを定義するノードを表し、以下のようなフィールドを持ちます。

```go
type GenDecl struct {
        Doc    *CommentGroup // associated documentation; or nil
        TokPos token.Pos     // position of Tok
        Tok    token.Token   // IMPORT, CONST, TYPE, VAR
        Lparen token.Pos     // position of '(', if any
        Specs  []Spec
        Rparen token.Pos // position of ')', if any
}
```

なお、`Tok`フィールドは以下の4種類の値のうちどれかで、それぞれは対応する[`ast.Spec`](https://golang.org/pkg/go/ast/#Spec)を持ちます。

* [`token.IMPORT`](https://golang.org/pkg/go/token/#IMPORT) : [`*ast.ImportSpec`](https://golang.org/pkg/go/ast/#ImportSpec)
* [`token.CONST`](https://golang.org/pkg/go/token/#CONST) :[`*ast.ValueSpec`](https://golang.org/pkg/go/ast/#ValueSpec)
* [`token.TYPE`](https://golang.org/pkg/go/token/#TYPE)  : [`*ast.TypeSpec`](https://golang.org/pkg/go/ast/#TypeSpec)
* [`token.VAR`](https://golang.org/pkg/go/token/#VAR)   : [`*ast.ValueSpec`](https://golang.org/pkg/go/ast/#ValueSpec)

この場合は、`import`なので、`Tok`フィールドは`token.IMPORT`になり、用いる`ast.Spec`インタフェースを満たす型は`ast.ImportSpec`構造体のポインタとなります。

ここまでのコードをまとめると、インポートの`ast.GenDecl`の初期化は以下のようになります。

```go
&ast.GenDecl{
	Tok: token.IMPORT,
	Specs: []ast.Spec{
		&ast.ImportSpec{
			// TODO: フィールドを埋める
		},
	},
}
```

さて、`ast.ImportSpec`構造体のフィールドはどう初期化すればよいでしょうか？
`ast.ImportSpec`構造体は以下のようなフィールドを持ちます。

```go
type ImportSpec struct {
        Doc     *CommentGroup // associated documentation; or nil
        Name    *Ident        // local package name (including "."); or nil
        Path    *BasicLit     // import path
        Comment *CommentGroup // line comments; or nil
        EndPos  token.Pos     // end of spec (overrides Path.Pos if nonzero)
}
```

`Name`フィールドは、インポートした際につけるファイルスコープで有効な別名でしょう。
`Path`フィールドには、インポートパスを書けば良さそうです。
[`ast.BasicLit`](https://golang.org/pkg/go/ast/#BasicLit)構造体のポインタ型の値をとるようなので、以下のようなに文字列を指定すれば良さそうです。

```go
&ast.ImportSpec{
	Path: &ast.BasicLit{
		Kind:  token.STRING,
		Value: strconv.Quote("fmt"),
	},
}
```

この時、`Value`フィールドには、`""`でくくられた文字列を指定する必要があるため、[`strconv`](https://golang.org/pkg/strconv/)パッケージの[`strconv.Quote`](https://golang.org/pkg/strconv/#Quote)関数を用いる必要があります。

これで`fmt`パッケージをインポートすることができました。

## `main`関数を作る

続いて`main`関数を作りましょう。
関数定義も、`ast.File`構造体の`Decls`フィールドの要素として、[`ast.FuncDecl`](https://golang.org/pkg/go/ast/#FuncDecl)構造体のポインタ型の値を設定しておけば良さそうです。

`ast.FuncDecl`構造体には以下のようなフィールドがあります。

```go
type FuncDecl struct {
        Doc  *CommentGroup // associated documentation; or nil
        Recv *FieldList    // receiver (methods); or nil (functions)
        Name *Ident        // function/method name
        Type *FuncType     // function signature: parameters, results, and position of "func" keyword
        Body *BlockStmt    // function body; or nil (forward declaration)
}
```

今回はメソッドではないため、`Recv`フィールドは無視して良さそうです。
関数名を`Name`フィールドで指定し、関数の本体は`Body`フィールドで指定すれば良さそうです。
また、`Type`フィールドには、[`ast.FuncType`](https://golang.org/pkg/go/ast/#FuncType)構造体のポインタ型の値として、シグニチャを設定する必要があります。

`ast.FuncType`構造体には、以下のようなフィールドがあります。

```go
type FuncType struct {
        Func    token.Pos  // position of "func" keyword (token.NoPos if there is no "func")
        Params  *FieldList // (incoming) parameters; non-nil
        Results *FieldList // (outgoing) results; or nil
}
```

今回の`main`関数は、引数も戻り値もないので、何もフィールドは指定しなくても良さそうです。

ここまでのコードをまとめると以下のようになります。

```go
&ast.FuncDecl{
	Name: ast.NewIdent("main"),
	Type: &ast.FuncType{},
	Body: /* TODO: 本体を設定する */,
}
```

関数の本体、つまり、`{`と`}`で囲まれた部分には、複文が設定されます。
複文は、[`ast.BlockStmt`](https://golang.org/pkg/go/ast/#BlockStmt)構造体で表現されます。

`ast.BlockStmt`構造体は以下のようなフィールドで構成されます。

```go
type BlockStmt struct {
        Lbrace token.Pos // position of "{"
        List   []Stmt
        Rbrace token.Pos // position of "}"
}
```

複文は文の集まりなので、`List`フィールドに文を表す[`ast.Stmt`](https://golang.org/pkg/go/ast/#Stmt)構造体のポインタ型のスライスを設定すれば良さそうです。
今回は、`fmt.Println("Hello, 世界")`だけなので、文は1つだけで良さそうです。

## `fmt.Println("Hello, 世界")`を呼び出す

さて、`fmt`パッケージの`Println`メソッドを呼び出す部分を書いていきましょう。
この文は、式のみで構成されるので[`ast.ExprStmt`](https://golang.org/pkg/go/ast/#ExprStmt)構造体を用いましょう。

`ast.ExprStmt`構造体は、式だけからなる文で以下のようなフィールドを用います。

```go
type ExprStmt struct {
        X Expr // expression
}
```

見て分かるとおり、式を表す[`ast.Expr`](https://golang.org/pkg/go/ast/#Expr)インタフェースを`X`フィールドとして保持しているだけです。

`fmt.Println`関数を呼び出すため、式の種類としては関数呼び出しを表す[`ast.CallExpr`](https://golang.org/pkg/go/ast/#CallExpr)構造体を用います。

`ast.CallExpr`構造体は以下のフィールドを持ちます。

```go
type CallExpr struct {
        Fun      Expr      // function expression
        Lparen   token.Pos // position of "("
        Args     []Expr    // function arguments; or nil
        Ellipsis token.Pos // position of "...", if any
        Rparen   token.Pos // position of ")"
}
```

`Fun`フィールドには、関数を参照するための式が入ります。
具体的には、関数名を表す`*ast.Ident`型やパッケージ関数やメソッドを表す[`*ast.SelectorExpr`](https://golang.org/pkg/go/ast/#SelectorExpr)、関数リテラルを表す[`*ast.FuncLit`](https://golang.org/pkg/go/ast/#FuncLit)が設定されます。

今回は、パッケージ関数を呼び出したいので、`ast.SelectorExpr`構造体を用います。
`ast.SelectorExpr`構造体は以下のようなフィールドで構成されています。

```go
type SelectorExpr struct {
        X   Expr   // expression
        Sel *Ident // field selector
}
```

`X`フィールドには、パッケージ名やレシーバを指定し、`Sel`フィールドには関数名やメソッド名を指定します。

さて、ここでもう一度`ast.CallExpr`構造体のフィールドをみてみましょう。

```go
type CallExpr struct {
        Fun      Expr      // function expression
        Lparen   token.Pos // position of "("
        Args     []Expr    // function arguments; or nil
        Ellipsis token.Pos // position of "...", if any
        Rparen   token.Pos // position of ")"
}
```

`fmt.Println`関数の引数として、`"Hello, 世界"`を指定する必要があるため、`ast.CallExpr`構造体の`Args`フィールドを設定する必要があります。

`"Hello, 世界"`は文字列なため、`ast.BasicLit`を用いれば良さそうです。

ここまでの処理をコードにまとめると以下のようになります。

```go
&ast.ExprStmt{
	X: &ast.CallExpr{
		Fun: &ast.SelectorExpr{
			X:   ast.NewIdent("fmt"),
			Sel: ast.NewIdent("Println"),
		},
		Args: []ast.Expr{
			&ast.BasicLit{
				Kind:  token.STRING,
				Value: strconv.Quote("Hello, 世界"),
			},
		},
	},
}
```

## ASTをコードにする

さて、これで`fmt`パッケージをインポートし、`fmt.Println`関数を呼び出す`main`関数を作ることができました。
それでは次に、ASTをコードとして出力してみましょう。

コードとして出力するには、「[抽象構文木（AST）をいじってフォーマットをかける ](http://qiita.com/tenntenn/items/8953f2ae80c610b353c8)」という記事で解説した、[`go/format`](https://golang.org/pkg/go/format/)パッケージの[`format.Node`](https://golang.org/pkg/go/format/)関数を用いれば良さそうです。

```go
format.Node(os.Stdout, token.NewFileSet(), f)
```

なお、`token.FileSet`はここでは後で使用しないので、引数に直接渡しています。

さて、ここまでのすべての処理をまとめると以下のようのなコードになります。

```go
package main

import (
	"go/ast"
	"go/format"
	"go/token"
	"os"
	"strconv"
)

func main() {
	f := &ast.File{
		Name: ast.NewIdent("main"),
		Decls: []ast.Decl{
			&ast.GenDecl{
				Tok: token.IMPORT,
				Specs: []ast.Spec{
					&ast.ImportSpec{
						Path: &ast.BasicLit{
							Kind:  token.STRING,
							Value: strconv.Quote("fmt"),
						},
					},
				},
			},
			&ast.FuncDecl{
				Name: ast.NewIdent("main"),
				Type: &ast.FuncType{},
				Body: &ast.BlockStmt{
					List: []ast.Stmt{
						&ast.ExprStmt{
							X: &ast.CallExpr{
								Fun: &ast.SelectorExpr{
									X:   ast.NewIdent("fmt"),
									Sel: ast.NewIdent("Println"),
								},
								Args: []ast.Expr{
									&ast.BasicLit{
										Kind:  token.STRING,
										Value: strconv.Quote("Hello, 世界"),
									},
								},
							},
						},
					},
				},
			},
		},
	}

	format.Node(os.Stdout, token.NewFileSet(), f)
}
```

出力結果もみてみましょう。
なお、このコードは[The Go Playground](https://play.golang.org/p/KjNsYif97E)でも動かすことができます。

```go:出力結果
package main

import "fmt"

func main() {
	fmt.Println("Hello, 世界")
}
```

うまく目的のコードが出力できましたね。

## おわりに

今回は、ASTをひとつずつ構築していくことで、`Hello, World`プログラムがどのようなノードで構成されているか解説しました。

みなさんもぜひもっと複雑なコードのASTを手入力してみて、どういう構造になっているのか実感してみてください。