はじめに
- サーバーサイドをGoでクライアントサイドをUnityでアプリを作っていて、インターフェイスの定義やマスターの構造、定数などをサーバーとクライアントで共有したくなった
- そこでGoのソースコードをパースしてクライアントサイドのソースコードを生成する go-langconv を作ってみた
go-langconv について
- Goのソースコードと設定ファイルを指定してクライアントのソースファイルを生成する
- 設定ファイルは TOML で記述し変換後のテンプレートと型の対応表を記載する
簡単な解説
- Goのソースコードの解析には
go/parser
go/ast
go/token
で行いtext/template
でクライアントサイドのソースコードを生成している
ソースコードの解析について
-
parser.ParseFile
にソースコードのファイルか文字列を渡すと*ast.File
としてコードをツリー構造にした構造体が返却される - 返却された
*ast.File
をast.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;
}