Go言語のコードから他の言語のコードを生成する

# はじめに

* サーバーサイドをGoでクライアントサイドをUnityでアプリを作っていて、インターフェイスの定義やマスターの構造、定数などをサーバーとクライアントで共有したくなった
* そこでGoのソースコードをパースしてクライアントサイドのソースコードを生成する [go-langconv](https://github.com/nirasan/go-langconv) を作ってみた

# go-langconv について

* Goのソースコードと設定ファイルを指定してクライアントのソースファイルを生成する
* 設定ファイルは [TOML](https://github.com/toml-lang/toml) で記述し変換後のテンプレートと型の対応表を記載する

# 簡単な解説

* Goのソースコードの解析には `go/parser` `go/ast` `go/token` で行い `text/template` でクライアントサイドのソースコードを生成している

## ソースコードの解析について

* `parser.ParseFile` にソースコードのファイルか文字列を渡すと `*ast.File` としてコードをツリー構造にした構造体が返却される
* 返却された `*ast.File` を `ast.Print` に渡すとツリー構造の中身を表示してくれる。
* 以上の Playground でのサンプルは[こちら](https://play.golang.org/p/DTzcZAjCF8)
* 構造体宣言や定数宣言などそれぞれツリーの構造は決まってくるので `ast.Print` の結果を見なたら構造体名やフィールド名や型、定数名や型などを取得する

### ツリー構造の例

* 例えば構造体宣言は以下のツリー構造になる

#### 元のコード

```go
package main
type User struct {
  Name string
  Age int32
}
```

#### ツリー構造

* `*ast.File.Decls` にコードのトップレベルの宣言のリストが入る
* 構造体宣言は `Decls[0]` になり `*ast.GenDecl` にキャストして取得する
* 構造体名は `Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Name` になる
* フィールド情報は `Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Type.(*ast.StructType).Fields.List` にスライスで入っており `*ast.Field.Names[0].Name` と `*ast.Field.Type.Name` がそれぞれフィールド名と型名になる

```
     0  *ast.File {
     1  .  Package: 2:1
     2  .  Name: *ast.Ident {
     3  .  .  NamePos: 2:9
     4  .  .  Name: "main"
     5  .  }
     6  .  Decls: []ast.Decl (len = 3) {
     7  .  .  0: *ast.GenDecl {
     8  .  .  .  TokPos: 3:1
     9  .  .  .  Tok: type
    10  .  .  .  Lparen: -
    11  .  .  .  Specs: []ast.Spec (len = 1) {
    12  .  .  .  .  0: *ast.TypeSpec {
    13  .  .  .  .  .  Name: *ast.Ident {
    14  .  .  .  .  .  .  NamePos: 3:6
    15  .  .  .  .  .  .  Name: "User"
    16  .  .  .  .  .  .  Obj: *ast.Object {
    17  .  .  .  .  .  .  .  Kind: type
    18  .  .  .  .  .  .  .  Name: "User"
    19  .  .  .  .  .  .  .  Decl: *(obj @ 12)
    20  .  .  .  .  .  .  }
    21  .  .  .  .  .  }
    22  .  .  .  .  .  Type: *ast.StructType {
    23  .  .  .  .  .  .  Struct: 3:11
    24  .  .  .  .  .  .  Fields: *ast.FieldList {
    25  .  .  .  .  .  .  .  Opening: 3:18
    26  .  .  .  .  .  .  .  List: []*ast.Field (len = 2) {
    27  .  .  .  .  .  .  .  .  0: *ast.Field {
    28  .  .  .  .  .  .  .  .  .  Names: []*ast.Ident (len = 1) {
    29  .  .  .  .  .  .  .  .  .  .  0: *ast.Ident {
    30  .  .  .  .  .  .  .  .  .  .  .  NamePos: 4:3
    31  .  .  .  .  .  .  .  .  .  .  .  Name: "Name"
    32  .  .  .  .  .  .  .  .  .  .  .  Obj: *ast.Object {
    33  .  .  .  .  .  .  .  .  .  .  .  .  Kind: var
    34  .  .  .  .  .  .  .  .  .  .  .  .  Name: "Name"
    35  .  .  .  .  .  .  .  .  .  .  .  .  Decl: *(obj @ 27)
    36  .  .  .  .  .  .  .  .  .  .  .  }
    37  .  .  .  .  .  .  .  .  .  .  }
    38  .  .  .  .  .  .  .  .  .  }
    39  .  .  .  .  .  .  .  .  .  Type: *ast.Ident {
    40  .  .  .  .  .  .  .  .  .  .  NamePos: 4:8
    41  .  .  .  .  .  .  .  .  .  .  Name: "string"
    42  .  .  .  .  .  .  .  .  .  }
    43  .  .  .  .  .  .  .  .  }
    44  .  .  .  .  .  .  .  .  1: *ast.Field {
    45  .  .  .  .  .  .  .  .  .  Names: []*ast.Ident (len = 1) {
    46  .  .  .  .  .  .  .  .  .  .  0: *ast.Ident {
    47  .  .  .  .  .  .  .  .  .  .  .  NamePos: 5:3
    48  .  .  .  .  .  .  .  .  .  .  .  Name: "Age"
    49  .  .  .  .  .  .  .  .  .  .  .  Obj: *ast.Object {
    50  .  .  .  .  .  .  .  .  .  .  .  .  Kind: var
    51  .  .  .  .  .  .  .  .  .  .  .  .  Name: "Age"
    52  .  .  .  .  .  .  .  .  .  .  .  .  Decl: *(obj @ 44)
    53  .  .  .  .  .  .  .  .  .  .  .  }
    54  .  .  .  .  .  .  .  .  .  .  }
    55  .  .  .  .  .  .  .  .  .  }
    56  .  .  .  .  .  .  .  .  .  Type: *ast.Ident {
    57  .  .  .  .  .  .  .  .  .  .  NamePos: 5:7
    58  .  .  .  .  .  .  .  .  .  .  Name: "int32"
    59  .  .  .  .  .  .  .  .  .  }
    60  .  .  .  .  .  .  .  .  }
    61  .  .  .  .  .  .  .  }
    62  .  .  .  .  .  .  .  Closing: 6:1
    63  .  .  .  .  .  .  }
    64  .  .  .  .  .  .  Incomplete: false
    65  .  .  .  .  .  }
    66  .  .  .  .  }
    67  .  .  .  }
    68  .  .  .  Rparen: -
    69  .  .  }
```

# 使用例

## Goのソースコード

```go:sample.go
package main

const (
    CONST1 int32   = 1
    CONST2 string  = "hello hello hello"
    CONST3 float64 = 3.1412
)

type User struct {
    Username string
    Age      int
}
```

## `C#`向けの設定ファイル

```toml:config.toml
# 定数定義のテンプレート
ConstTemplate = '''
public static partial class Constant
{
{{ range . -}}
{{ "    " -}} public const {{ typeconv .Type }} {{ .Name }} = {{ .Value }};
{{ end -}}
}
'''

# クラス定義のテンプレート
StructTemplate = '''
public class {{ .Name }}
{
{{ range .Fields -}}
{{ "    " -}} public {{ typeconv .Type }} {{ .Name }};
{{ end -}}
}
'''

# 型の対応表
[Typemap]
int = "int"
int32 = "int"
int64 = "long"
uint = "uint"
uint32 = "uint"
uint64 = "ulong"
float32 = "float"
float64 = "double"
string = "string"
bool = "bool"
```

## 実行コマンド

```
langconv -f sample.go -c config.toml -o sample.cs
```

## 出力ファイル

```cs:sample.cs
public static partial class Constant
{
    public const int CONST1 = 1;
    public const string CONST2 = "hello hello hello";
    public const double CONST3 = 3.1412;
}
public class User
{
    public string Username;
    public int Age;
}
```