Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

はじめに

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

go-langconv について

  • Goのソースコードと設定ファイルを指定してクライアントのソースファイルを生成する
  • 設定ファイルは TOML で記述し変換後のテンプレートと型の対応表を記載する

簡単な解説

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

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

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

ツリー構造の例

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

元のコード

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のソースコード

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#向けの設定ファイル

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

出力ファイル

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;
}
nirasan
フリーで開発者をしています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away