Эх сурвалжийг харах

feature: refactor api parse to g4 (#365)

* feature: refactor api parse to g4

* new g4 parser

* add CHANGE_LOG.MD

* refactor

* fix byte bug

* refactor

* optimized

* optimized

* revert

* update readme.md

* update readme.md

* update readme.md

* update readme.md

* remove no need

* fix java gen

* add upgrade

* resolve confilits

Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
kingxt 4 жил өмнө
parent
commit
ee19fb736b
88 өөрчлөгдсөн 13621 нэмэгдсэн , 2438 устгасан
  1. 1 0
      go.mod
  2. 2 0
      go.sum
  3. 15 0
      tools/goctl/CHANGE_LOG.MD
  4. 6 3
      tools/goctl/api/apigen/gen.go
  5. 1 5
      tools/goctl/api/dartgen/gen.go
  6. 0 1
      tools/goctl/api/dartgen/gendata.go
  7. 0 43
      tools/goctl/api/dartgen/util.go
  8. 22 4
      tools/goctl/api/docgen/doc.go
  9. 2 5
      tools/goctl/api/docgen/gen.go
  10. 5 1
      tools/goctl/api/format/format.go
  11. 4 8
      tools/goctl/api/format/format_test.go
  12. 1 5
      tools/goctl/api/gogen/gen.go
  13. 27 71
      tools/goctl/api/gogen/gen_test.go
  14. 2 9
      tools/goctl/api/gogen/genetc.go
  15. 9 23
      tools/goctl/api/gogen/genhandlers.go
  16. 15 12
      tools/goctl/api/gogen/genlogic.go
  17. 14 13
      tools/goctl/api/gogen/genroutes.go
  18. 13 51
      tools/goctl/api/gogen/gentypes.go
  19. 79 9
      tools/goctl/api/gogen/util.go
  20. 1 5
      tools/goctl/api/javagen/gen.go
  21. 80 46
      tools/goctl/api/javagen/gencomponents.go
  22. 60 100
      tools/goctl/api/javagen/genpacket.go
  23. 90 64
      tools/goctl/api/javagen/util.go
  24. 1 5
      tools/goctl/api/ktgen/cmd.go
  25. 43 1
      tools/goctl/api/ktgen/funcs.go
  26. 17 2
      tools/goctl/api/ktgen/gen.go
  27. 0 268
      tools/goctl/api/parser/apifileparser.go
  28. 0 236
      tools/goctl/api/parser/basestate.go
  29. 0 20
      tools/goctl/api/parser/basestate_test.go
  30. 0 146
      tools/goctl/api/parser/entity.go
  31. 46 0
      tools/goctl/api/parser/g4/ApiLexer.g4
  32. 73 0
      tools/goctl/api/parser/g4/ApiParser.g4
  33. 251 0
      tools/goctl/api/parser/g4/ast/api.go
  34. 405 0
      tools/goctl/api/parser/g4/ast/apiparser.go
  35. 325 0
      tools/goctl/api/parser/g4/ast/ast.go
  36. 96 0
      tools/goctl/api/parser/g4/ast/import.go
  37. 67 0
      tools/goctl/api/parser/g4/ast/info.go
  38. 79 0
      tools/goctl/api/parser/g4/ast/kv.go
  39. 5 0
      tools/goctl/api/parser/g4/ast/placeholder.go
  40. 603 0
      tools/goctl/api/parser/g4/ast/service.go
  41. 58 0
      tools/goctl/api/parser/g4/ast/syntax.go
  42. 677 0
      tools/goctl/api/parser/g4/ast/type.go
  43. 156 0
      tools/goctl/api/parser/g4/gen/api/apiparser_base_visitor.go
  44. 234 0
      tools/goctl/api/parser/g4/gen/api/apiparser_lexer.go
  45. 5688 0
      tools/goctl/api/parser/g4/gen/api/apiparser_parser.go
  46. 120 0
      tools/goctl/api/parser/g4/gen/api/apiparser_visitor.go
  47. 222 0
      tools/goctl/api/parser/g4/gen/api/baseparser.go
  48. 11 0
      tools/goctl/api/parser/g4/gen/api/baseparser_test.go
  49. 99 0
      tools/goctl/api/parser/g4/test.api
  50. 369 0
      tools/goctl/api/parser/g4/test/apiparser_test.go
  51. 0 0
      tools/goctl/api/parser/g4/test/apis/empty.api
  52. 72 0
      tools/goctl/api/parser/g4/test/apis/example.api
  53. 6 0
      tools/goctl/api/parser/g4/test/apis/info.api
  54. 29 0
      tools/goctl/api/parser/g4/test/apis/service.api
  55. 1 0
      tools/goctl/api/parser/g4/test/apis/syntax.api
  56. 111 0
      tools/goctl/api/parser/g4/test/apis/test.api
  57. 23 0
      tools/goctl/api/parser/g4/test/apis/types.api
  58. 457 0
      tools/goctl/api/parser/g4/test/ast_test.go
  59. 25 0
      tools/goctl/api/parser/g4/test/grammar_test.go
  60. 143 0
      tools/goctl/api/parser/g4/test/import_test.go
  61. 186 0
      tools/goctl/api/parser/g4/test/info_test.go
  62. 686 0
      tools/goctl/api/parser/g4/test/service_test.go
  63. 75 0
      tools/goctl/api/parser/g4/test/syntax_test.go
  64. 467 0
      tools/goctl/api/parser/g4/test/type_test.go
  65. 0 62
      tools/goctl/api/parser/infostate.go
  66. 249 60
      tools/goctl/api/parser/parser.go
  67. 682 0
      tools/goctl/api/parser/readme.md
  68. 0 113
      tools/goctl/api/parser/rootstate.go
  69. 0 130
      tools/goctl/api/parser/servicestate.go
  70. 0 7
      tools/goctl/api/parser/state.go
  71. 0 339
      tools/goctl/api/parser/typeparser.go
  72. 0 58
      tools/goctl/api/parser/util.go
  73. 0 55
      tools/goctl/api/parser/validator.go
  74. 0 20
      tools/goctl/api/parser/vars.go
  75. 79 4
      tools/goctl/api/spec/fn.go
  76. 25 0
      tools/goctl/api/spec/name.go
  77. 55 74
      tools/goctl/api/spec/spec.go
  78. 2 5
      tools/goctl/api/tsgen/gen.go
  79. 5 17
      tools/goctl/api/tsgen/gencomponents.go
  80. 36 73
      tools/goctl/api/tsgen/genpacket.go
  81. 62 69
      tools/goctl/api/tsgen/util.go
  82. 0 20
      tools/goctl/api/util/annotation.go
  83. 23 3
      tools/goctl/api/util/case.go
  84. 0 159
      tools/goctl/api/util/types.go
  85. 1 5
      tools/goctl/api/validate/validate.go
  86. 6 0
      tools/goctl/goctl.go
  87. 3 9
      tools/goctl/plugin/plugin.go
  88. 18 0
      tools/goctl/upgrade/upgrade.go

+ 1 - 0
go.mod

@@ -6,6 +6,7 @@ require (
 	github.com/ClickHouse/clickhouse-go v1.4.3
 	github.com/DATA-DOG/go-sqlmock v1.4.1
 	github.com/alicebob/miniredis/v2 v2.14.1
+	github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2
 	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
 	github.com/dchest/siphash v1.2.1
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible

+ 2 - 0
go.sum

@@ -16,6 +16,8 @@ github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGn
 github.com/alicebob/miniredis/v2 v2.14.1 h1:GjlbSeoJ24bzdLRs13HoMEeaRZx9kg5nHoRW7QV/nCs=
 github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg=
 github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
+github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2 h1:rL2miklL5rhxUaZO7hntBcy/VHaiyuPQ4EJoy/NMwaM=
+github.com/antlr/antlr4 v0.0.0-20210105212045-464bcbc32de2/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

+ 15 - 0
tools/goctl/CHANGE_LOG.MD

@@ -0,0 +1,15 @@
+# 2020-01-08
+
+## features ![](https://img.shields.io/static/v1?label=&message=new&color=red)
+
+* reactor api parse by g4
+* add syntax lexer for api
+* support java-style documentation comments
+* support parsing doc and comment
+* support import group
+
+> original: [api grammar document](./api/parser/readme.md)
+
+# 2020-01-08
+
+* add change log

+ 6 - 3
tools/goctl/api/apigen/gen.go

@@ -12,18 +12,21 @@ import (
 	"github.com/urfave/cli"
 )
 
-const apiTemplate = `info(
+const apiTemplate = `
+syntax = "v1"
+
+info(
 	title: // TODO: add title
 	desc: // TODO: add description
 	author: "{{.gitUser}}"
 	email: "{{.gitEmail}}"
 )
 
-type request struct {
+type request {
 	// TODO: add members here and delete this comment
 }
 
-type response struct {
+type response {
 	// TODO: add members here and delete this comment
 }
 

+ 1 - 5
tools/goctl/api/dartgen/gen.go

@@ -19,11 +19,7 @@ func DartCommand(c *cli.Context) error {
 		return errors.New("missing -dir")
 	}
 
-	p, err := parser.NewParser(apiFile)
-	if err != nil {
-		return err
-	}
-	api, err := p.Parse()
+	api, err := parser.Parse(apiFile)
 	if err != nil {
 		return err
 	}

+ 0 - 1
tools/goctl/api/dartgen/gendata.go

@@ -59,7 +59,6 @@ func genData(dir string, api *spec.ApiSpec) error {
 		return e
 	}
 
-	convertMemberType(api)
 	return t.Execute(file, api)
 }
 

+ 0 - 43
tools/goctl/api/dartgen/util.go

@@ -1,12 +1,10 @@
 package dartgen
 
 import (
-	"log"
 	"os"
 	"reflect"
 	"strings"
 
-	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
 	"github.com/tal-tech/go-zero/tools/goctl/api/util"
 )
 
@@ -39,47 +37,6 @@ func tagGet(tag, k string) (reflect.Value, error) {
 	return reflect.ValueOf(out), nil
 }
 
-func convertMemberType(api *spec.ApiSpec) {
-	for i, t := range api.Types {
-		for j, mem := range t.Members {
-			api.Types[i].Members[j].Type = goTypeToDart(mem.Type)
-		}
-	}
-}
-
-func goTypeToDart(t string) string {
-	t = strings.Replace(t, "*", "", -1)
-	if strings.HasPrefix(t, "[]") {
-		return "List<" + goTypeToDart(t[2:]) + ">"
-	}
-
-	if strings.HasPrefix(t, "map") {
-		tys, e := util.DecomposeType(t)
-		if e != nil {
-			log.Fatal(e)
-		}
-
-		if len(tys) != 2 {
-			log.Fatal("Map type number !=2")
-		}
-
-		return "Map<String," + goTypeToDart(tys[1]) + ">"
-	}
-
-	switch t {
-	case "string":
-		return "String"
-	case "int", "int32", "int64":
-		return "int"
-	case "float", "float32", "float64":
-		return "double"
-	case "bool":
-		return "bool"
-	default:
-		return t
-	}
-}
-
 func isDirectType(s string) bool {
 	return isAtomicType(s) || isListType(s) && isAtomicType(getCoreType(s))
 }

+ 22 - 4
tools/goctl/api/docgen/doc.go

@@ -41,7 +41,7 @@ func genDoc(api *spec.ApiSpec, dir string, filename string) error {
 
 	var builder strings.Builder
 	for index, route := range api.Service.Routes() {
-		routeComment, _ := util.GetAnnotationValue(route.Annotations, "doc", "summary")
+		routeComment := route.JoinedDoc()
 		if len(routeComment) == 0 {
 			routeComment = "N/A"
 		}
@@ -58,8 +58,8 @@ func genDoc(api *spec.ApiSpec, dir string, filename string) error {
 			"routeComment":    routeComment,
 			"method":          strings.ToUpper(route.Method),
 			"uri":             route.Path,
-			"requestType":     "`" + stringx.TakeOne(route.RequestType.Name, "-") + "`",
-			"responseType":    "`" + stringx.TakeOne(route.ResponseType.Name, "-") + "`",
+			"requestType":     "`" + stringx.TakeOne(route.RequestTypeName(), "-") + "`",
+			"responseType":    "`" + stringx.TakeOne(route.ResponseTypeName(), "-") + "`",
 			"responseContent": responseContent,
 		})
 		if err != nil {
@@ -73,10 +73,28 @@ func genDoc(api *spec.ApiSpec, dir string, filename string) error {
 }
 
 func responseBody(api *spec.ApiSpec, route spec.Route) (string, error) {
-	tps := util.GetLocalTypes(api, route)
+	if len(route.ResponseTypeName()) == 0 {
+		return "", nil
+	}
+
+	var tps = make([]spec.Type, 0)
+	tps = append(tps, route.ResponseType)
+	if definedType, ok := route.ResponseType.(spec.DefineStruct); ok {
+		associatedTypes(definedType, &tps)
+	}
 	value, err := gogen.BuildTypes(tps)
 	if err != nil {
 		return "", err
 	}
+
 	return fmt.Sprintf("\n\n```golang\n%s\n```\n", value), nil
 }
+
+func associatedTypes(tp spec.DefineStruct, tps *[]spec.Type) {
+	*tps = append(*tps, tp)
+	for _, item := range tp.Members {
+		if definedType, ok := item.Type.(spec.DefineStruct); ok {
+			associatedTypes(definedType, tps)
+		}
+	}
+}

+ 2 - 5
tools/goctl/api/docgen/gen.go

@@ -29,14 +29,11 @@ func DocCommand(c *cli.Context) error {
 		return err
 	}
 	for _, f := range files {
-		p, err := parser.NewParser(f)
+		api, err := parser.Parse(f)
 		if err != nil {
 			return errors.New(fmt.Sprintf("parse file: %s, err: %s", f, err.Error()))
 		}
-		api, err := p.Parse()
-		if err != nil {
-			return err
-		}
+
 		index := strings.Index(f, dir)
 		if index < 0 {
 			continue

+ 5 - 1
tools/goctl/api/format/format.go

@@ -97,7 +97,7 @@ func ApiFormatByPath(apiFilePath string) error {
 }
 
 func apiFormat(data string) (string, error) {
-	_, err := parser.ParseApi(data)
+	_, err := parser.ParseContent(data)
 	if err != nil {
 		return "", err
 	}
@@ -208,5 +208,9 @@ func mayInsertStructKeyword(line string, token *int) string {
 	if strings.HasSuffix(noCommentLine, leftParenthesis) {
 		*token++
 	}
+
+	if strings.Contains(noCommentLine, "`") {
+		return util.UpperFirst(strings.TrimSpace(line))
+	}
 	return line
 }

+ 4 - 8
tools/goctl/api/format/format_test.go

@@ -9,13 +9,11 @@ import (
 const (
 	notFormattedStr = `
 type Request struct {
-  Name string 
+  Name string ` + "`" + `path:"name,options=you|me"` + "`" + `  
 }
-
 type Response struct {
-  Message string
+  Message string ` + "`" + `json:"message"` + "`" + `
 }
-
 service A-api {
 @server(
 handler: GreetHandler
@@ -25,13 +23,11 @@ handler: GreetHandler
 `
 
 	formattedStr = `type Request {
-	Name string
+	Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
 }
-
 type Response {
-	Message string
+	Message string ` + "`" + `json:"message"` + "`" + `
 }
-
 service A-api {
 	@server(
 		handler: GreetHandler

+ 1 - 5
tools/goctl/api/gogen/gen.go

@@ -41,11 +41,7 @@ func GoCommand(c *cli.Context) error {
 }
 
 func DoGenProject(apiFile, dir, style string) error {
-	p, err := parser.NewParser(apiFile)
-	if err != nil {
-		return err
-	}
-	api, err := p.Parse()
+	api, err := parser.Parse(apiFile)
 	if err != nil {
 		return err
 	}

+ 27 - 71
tools/goctl/api/gogen/gen_test.go

@@ -16,9 +16,9 @@ import (
 const testApiTemplate = `
 info(
     title: doc title
-    desc: >
+    desc: ">
     doc description first part,
-    doc description second part<
+    doc description second part<"
     version: 1.0
 )
 
@@ -55,9 +55,7 @@ service A-api {
 const testMultiServiceTemplate = `
 info(
     title: doc title
-    desc: >
-    doc description first part,
-    doc description second part<
+    desc: doc description first part
     version: 1.0
 )
 
@@ -229,7 +227,7 @@ type Response struct {
 }
 
 service A-api {
-  @doc(helloworld)
+  @doc ("helloworld")
   @server(
     handler: GreetHandler
   )
@@ -249,7 +247,7 @@ type Response struct {
 }
 
 service A-api {
-  @doc(helloworld)
+  @doc ("helloworld")
   @server(
     handler: GreetHandler
   )
@@ -325,10 +323,7 @@ func TestParser(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	api, err := parser.Parse()
+	api, err := parser.Parse(filename)
 	assert.Nil(t, err)
 
 	assert.Equal(t, len(api.Types), 2)
@@ -337,8 +332,8 @@ func TestParser(t *testing.T) {
 	assert.Equal(t, api.Service.Routes()[0].Path, "/greet/from/:name")
 	assert.Equal(t, api.Service.Routes()[1].Path, "/greet/get")
 
-	assert.Equal(t, api.Service.Routes()[1].RequestType.Name, "Request")
-	assert.Equal(t, api.Service.Routes()[1].ResponseType.Name, "")
+	assert.Equal(t, api.Service.Routes()[1].RequestTypeName(), "Request")
+	assert.Equal(t, api.Service.Routes()[1].ResponseType, nil)
 
 	validate(t, filename)
 }
@@ -349,10 +344,7 @@ func TestMultiService(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	api, err := parser.Parse()
+	api, err := parser.Parse(filename)
 	assert.Nil(t, err)
 
 	assert.Equal(t, len(api.Service.Routes()), 2)
@@ -367,10 +359,7 @@ func TestApiNoInfo(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	_, err = parser.Parse()
+	_, err = parser.Parse(filename)
 	assert.Nil(t, err)
 
 	validate(t, filename)
@@ -382,7 +371,7 @@ func TestInvalidApiFile(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	_, err = parser.NewParser(filename)
+	_, err = parser.Parse(filename)
 	assert.NotNil(t, err)
 }
 
@@ -392,14 +381,11 @@ func TestAnonymousAnnotation(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	api, err := parser.Parse()
+	api, err := parser.Parse(filename)
 	assert.Nil(t, err)
 
 	assert.Equal(t, len(api.Service.Routes()), 1)
-	assert.Equal(t, api.Service.Routes()[0].Annotations[0].Value, "GreetHandler")
+	assert.Equal(t, api.Service.Routes()[0].Handler, "GreetHandler")
 
 	validate(t, filename)
 }
@@ -410,10 +396,7 @@ func TestApiHasMiddleware(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	_, err = parser.Parse()
+	_, err = parser.Parse(filename)
 	assert.Nil(t, err)
 
 	validate(t, filename)
@@ -425,10 +408,7 @@ func TestApiHasJwt(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	_, err = parser.Parse()
+	_, err = parser.Parse(filename)
 	assert.Nil(t, err)
 
 	validate(t, filename)
@@ -440,10 +420,7 @@ func TestApiHasJwtAndMiddleware(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	_, err = parser.Parse()
+	_, err = parser.Parse(filename)
 	assert.Nil(t, err)
 
 	validate(t, filename)
@@ -455,13 +432,8 @@ func TestApiHasNoRequestBody(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	_, err = parser.Parse()
+	_, err = parser.Parse(filename)
 	assert.Nil(t, err)
-
-	validate(t, filename)
 }
 
 func TestApiRoutes(t *testing.T) {
@@ -470,10 +442,7 @@ func TestApiRoutes(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	_, err = parser.Parse()
+	_, err = parser.Parse(filename)
 	assert.Nil(t, err)
 
 	validate(t, filename)
@@ -485,10 +454,7 @@ func TestHasCommentRoutes(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	_, err = parser.Parse()
+	_, err = parser.Parse(filename)
 	assert.Nil(t, err)
 
 	validate(t, filename)
@@ -500,13 +466,8 @@ func TestInlineTypeNotExist(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	_, err = parser.Parse()
-	assert.Nil(t, err)
-
-	validate(t, filename)
+	_, err = parser.Parse(filename)
+	assert.NotNil(t, err)
 }
 
 func TestHasImportApi(t *testing.T) {
@@ -520,15 +481,12 @@ func TestHasImportApi(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(importApiName)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	api, err := parser.Parse()
+	api, err := parser.Parse(filename)
 	assert.Nil(t, err)
 
 	var hasInline bool
 	for _, ty := range api.Types {
-		if ty.Name == "ImportData" {
+		if ty.Name() == "ImportData" {
 			hasInline = true
 			break
 		}
@@ -544,10 +502,7 @@ func TestNoStructApi(t *testing.T) {
 	assert.Nil(t, err)
 	defer os.Remove(filename)
 
-	parser, err := parser.NewParser(filename)
-	assert.Nil(t, err)
-
-	spec, err := parser.Parse()
+	spec, err := parser.Parse(filename)
 	assert.Nil(t, err)
 	assert.Equal(t, len(spec.Types), 5)
 
@@ -559,8 +514,8 @@ func TestNestTypeApi(t *testing.T) {
 	err := ioutil.WriteFile(filename, []byte(nestTypeApi), os.ModePerm)
 	assert.Nil(t, err)
 	defer os.Remove(filename)
-	_, err = parser.NewParser(filename)
 
+	_, err = parser.Parse(filename)
 	assert.NotNil(t, err)
 }
 
@@ -569,7 +524,8 @@ func TestCamelStyle(t *testing.T) {
 	err := ioutil.WriteFile(filename, []byte(testApiTemplate), os.ModePerm)
 	assert.Nil(t, err)
 	defer os.Remove(filename)
-	_, err = parser.NewParser(filename)
+
+	_, err = parser.Parse(filename)
 	assert.Nil(t, err)
 
 	validateWithCamel(t, filename, "GoZero")

+ 2 - 9
tools/goctl/api/gogen/genetc.go

@@ -5,7 +5,6 @@ import (
 	"strconv"
 
 	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-	"github.com/tal-tech/go-zero/tools/goctl/api/util"
 	"github.com/tal-tech/go-zero/tools/goctl/config"
 	"github.com/tal-tech/go-zero/tools/goctl/util/format"
 )
@@ -26,14 +25,8 @@ func genEtc(dir string, cfg *config.Config, api *spec.ApiSpec) error {
 	}
 
 	service := api.Service
-	host, ok := util.GetAnnotationValue(service.Groups[0].Annotations, "server", "host")
-	if !ok {
-		host = "0.0.0.0"
-	}
-	port, ok := util.GetAnnotationValue(service.Groups[0].Annotations, "server", "port")
-	if !ok {
-		port = strconv.Itoa(defaultPort)
-	}
+	host := "0.0.0.0"
+	port := strconv.Itoa(defaultPort)
 
 	return genFile(fileGenConfig{
 		dir:             dir,

+ 9 - 23
tools/goctl/api/gogen/genhandlers.go

@@ -1,14 +1,11 @@
 package gogen
 
 import (
-	"errors"
 	"fmt"
 	"path"
 	"strings"
-	"unicode"
 
 	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-	apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
 	"github.com/tal-tech/go-zero/tools/goctl/config"
 	"github.com/tal-tech/go-zero/tools/goctl/util"
 	"github.com/tal-tech/go-zero/tools/goctl/util/format"
@@ -65,11 +62,11 @@ func genHandler(dir string, cfg *config.Config, group spec.Group, route spec.Rou
 	return doGenToFile(dir, handler, cfg, group, route, Handler{
 		ImportPackages: genHandlerImports(group, route, parentPkg),
 		HandlerName:    handler,
-		RequestType:    util.Title(route.RequestType.Name),
+		RequestType:    util.Title(route.RequestTypeName()),
 		LogicType:      strings.Title(getLogicName(route)),
 		Call:           strings.Title(strings.TrimSuffix(handler, "Handler")),
-		HasResp:        len(route.ResponseType.Name) > 0,
-		HasRequest:     len(route.RequestType.Name) > 0,
+		HasResp:        len(route.ResponseTypeName()) > 0,
+		HasRequest:     len(route.RequestTypeName()) > 0,
 	})
 }
 
@@ -109,7 +106,7 @@ func genHandlerImports(group spec.Group, route spec.Route, parentPkg string) str
 	imports = append(imports, fmt.Sprintf("\"%s\"",
 		util.JoinPackages(parentPkg, getLogicFolderPath(group, route))))
 	imports = append(imports, fmt.Sprintf("\"%s\"", util.JoinPackages(parentPkg, contextDir)))
-	if len(route.RequestType.Name) > 0 {
+	if len(route.RequestTypeName()) > 0 {
 		imports = append(imports, fmt.Sprintf("\"%s\"\n", util.JoinPackages(parentPkg, typesDir)))
 	}
 	imports = append(imports, fmt.Sprintf("\"%s/rest/httpx\"", vars.ProjectOpenSourceUrl))
@@ -118,18 +115,7 @@ func genHandlerImports(group spec.Group, route spec.Route, parentPkg string) str
 }
 
 func getHandlerBaseName(route spec.Route) (string, error) {
-	handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
-	if !ok {
-		return "", fmt.Errorf("missing handler annotation for %q", route.Path)
-	}
-
-	for _, char := range handler {
-		if !unicode.IsDigit(char) && !unicode.IsLetter(char) {
-			return "", errors.New(fmt.Sprintf("route [%s] handler [%s] invalid, handler name should only contains letter or digit",
-				route.Path, handler))
-		}
-	}
-
+	handler := route.Handler
 	handler = strings.TrimSpace(handler)
 	handler = strings.TrimSuffix(handler, "handler")
 	handler = strings.TrimSuffix(handler, "Handler")
@@ -137,10 +123,10 @@ func getHandlerBaseName(route spec.Route) (string, error) {
 }
 
 func getHandlerFolderPath(group spec.Group, route spec.Route) string {
-	folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", groupProperty)
-	if !ok {
-		folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", groupProperty)
-		if !ok {
+	folder := route.GetAnnotation(groupProperty)
+	if len(folder) == 0 {
+		folder = group.GetAnnotation(groupProperty)
+		if len(folder) == 0 {
 			return handlerDir
 		}
 	}

+ 15 - 12
tools/goctl/api/gogen/genlogic.go

@@ -6,7 +6,6 @@ import (
 	"strings"
 
 	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-	"github.com/tal-tech/go-zero/tools/goctl/api/util"
 	"github.com/tal-tech/go-zero/tools/goctl/config"
 	ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
 	"github.com/tal-tech/go-zero/tools/goctl/util/format"
@@ -68,16 +67,20 @@ func genLogicByRoute(dir string, cfg *config.Config, group spec.Group, route spe
 	var responseString string
 	var returnString string
 	var requestString string
-	if len(route.ResponseType.Name) > 0 {
-		resp := strings.Title(route.ResponseType.Name)
-		responseString = "(*types." + resp + ", error)"
-		returnString = fmt.Sprintf("return &types.%s{}, nil", resp)
+	if len(route.ResponseTypeName()) > 0 {
+		resp := responseGoTypeName(route, typesPacket)
+		responseString = "(" + resp + ", error)"
+		if strings.HasPrefix(resp, "*") {
+			returnString = fmt.Sprintf("return &%s{}, nil", strings.TrimPrefix(resp, "*"))
+		} else {
+			returnString = fmt.Sprintf("return %s{}, nil", resp)
+		}
 	} else {
 		responseString = "error"
 		returnString = "return nil"
 	}
-	if len(route.RequestType.Name) > 0 {
-		requestString = "req " + "types." + strings.Title(route.RequestType.Name)
+	if len(route.RequestTypeName()) > 0 {
+		requestString = "req " + requestGoTypeName(route, typesPacket)
 	}
 
 	return genFile(fileGenConfig{
@@ -100,10 +103,10 @@ func genLogicByRoute(dir string, cfg *config.Config, group spec.Group, route spe
 }
 
 func getLogicFolderPath(group spec.Group, route spec.Route) string {
-	folder, ok := util.GetAnnotationValue(route.Annotations, "server", groupProperty)
-	if !ok {
-		folder, ok = util.GetAnnotationValue(group.Annotations, "server", groupProperty)
-		if !ok {
+	folder := route.GetAnnotation(groupProperty)
+	if len(folder) == 0 {
+		folder = group.GetAnnotation(groupProperty)
+		if len(folder) == 0 {
 			return logicDir
 		}
 	}
@@ -116,7 +119,7 @@ func genLogicImports(route spec.Route, parentPkg string) string {
 	var imports []string
 	imports = append(imports, `"context"`+"\n")
 	imports = append(imports, fmt.Sprintf("\"%s\"", ctlutil.JoinPackages(parentPkg, contextDir)))
-	if len(route.ResponseType.Name) > 0 || len(route.RequestType.Name) > 0 {
+	if len(route.ResponseTypeName()) > 0 || len(route.RequestTypeName()) > 0 {
 		imports = append(imports, fmt.Sprintf("\"%s\"\n", ctlutil.JoinPackages(parentPkg, typesDir)))
 	}
 	imports = append(imports, fmt.Sprintf("\"%s/core/logx\"", vars.ProjectOpenSourceUrl))

+ 14 - 13
tools/goctl/api/gogen/genroutes.go

@@ -10,7 +10,6 @@ import (
 
 	"github.com/tal-tech/go-zero/core/collection"
 	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-	apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
 	"github.com/tal-tech/go-zero/tools/goctl/config"
 	"github.com/tal-tech/go-zero/tools/goctl/util"
 	"github.com/tal-tech/go-zero/tools/goctl/util/format"
@@ -151,10 +150,10 @@ func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
 	importSet.AddStr(fmt.Sprintf("\"%s\"", util.JoinPackages(parentPkg, contextDir)))
 	for _, group := range api.Service.Groups {
 		for _, route := range group.Routes {
-			folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", groupProperty)
-			if !ok {
-				folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", groupProperty)
-				if !ok {
+			folder := route.GetAnnotation(groupProperty)
+			if len(folder) == 0 {
+				folder = group.GetAnnotation(groupProperty)
+				if len(folder) == 0 {
 					continue
 				}
 			}
@@ -176,12 +175,12 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
 		for _, r := range g.Routes {
 			handler := getHandlerName(r)
 			handler = handler + "(serverCtx)"
-			folder, ok := apiutil.GetAnnotationValue(r.Annotations, "server", groupProperty)
-			if ok {
+			folder := r.GetAnnotation(groupProperty)
+			if len(folder) > 0 {
 				handler = toPrefix(folder) + "." + strings.ToUpper(handler[:1]) + handler[1:]
 			} else {
-				folder, ok = apiutil.GetAnnotationValue(g.Annotations, "server", groupProperty)
-				if ok {
+				folder = g.GetAnnotation(groupProperty)
+				if len(folder) > 0 {
 					handler = toPrefix(folder) + "." + strings.ToUpper(handler[:1]) + handler[1:]
 				}
 			}
@@ -192,12 +191,14 @@ func getRoutes(api *spec.ApiSpec) ([]group, error) {
 			})
 		}
 
-		if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "jwt"); ok {
-			groupedRoutes.authName = value
+		jwt := g.GetAnnotation("jwt")
+		if len(jwt) > 0 {
+			groupedRoutes.authName = jwt
 			groupedRoutes.jwtEnabled = true
 		}
-		if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
-			for _, item := range strings.Split(value, ",") {
+		middleware := g.GetAnnotation("middleware")
+		if len(middleware) > 0 {
+			for _, item := range strings.Split(middleware, ",") {
 				groupedRoutes.middlewares = append(groupedRoutes.middlewares, item)
 			}
 		}

+ 13 - 51
tools/goctl/api/gogen/gentypes.go

@@ -35,8 +35,8 @@ func BuildTypes(types []spec.Type) (string, error) {
 		} else {
 			builder.WriteString("\n\n")
 		}
-		if err := writeType(&builder, tp, types); err != nil {
-			return "", apiutil.WrapErr(err, "Type "+tp.Name+" generate error")
+		if err := writeType(&builder, tp); err != nil {
+			return "", apiutil.WrapErr(err, "Type "+tp.Name()+" generate error")
 		}
 	}
 
@@ -53,6 +53,7 @@ func genTypes(dir string, cfg *config.Config, api *spec.ApiSpec) error {
 	if err != nil {
 		return err
 	}
+
 	typeFilename = typeFilename + ".go"
 	filename := path.Join(dir, typesDir, typeFilename)
 	os.Remove(filename)
@@ -67,67 +68,28 @@ func genTypes(dir string, cfg *config.Config, api *spec.ApiSpec) error {
 		builtinTemplate: typesTemplate,
 		data: map[string]interface{}{
 			"types":        val,
-			"containsTime": api.ContainsTime(),
+			"containsTime": false,
 		},
 	})
 }
 
-func convertTypeCase(types []spec.Type, t string) (string, error) {
-	ts, err := apiutil.DecomposeType(t)
-	if err != nil {
-		return "", err
-	}
-
-	var defTypes []string
-	for _, tp := range ts {
-		for _, typ := range types {
-			if typ.Name == tp {
-				defTypes = append(defTypes, tp)
-			}
-		}
-	}
-
-	for _, tp := range defTypes {
-		t = strings.ReplaceAll(t, tp, util.Title(tp))
+func writeType(writer io.Writer, tp spec.Type) error {
+	structType, ok := tp.(spec.DefineStruct)
+	if !ok {
+		return errors.New(fmt.Sprintf("unspport struct type: %s", tp.Name()))
 	}
 
-	return t, nil
-}
-
-func writeType(writer io.Writer, tp spec.Type, types []spec.Type) error {
-	fmt.Fprintf(writer, "type %s struct {\n", util.Title(tp.Name))
-	for _, member := range tp.Members {
+	fmt.Fprintf(writer, "type %s struct {\n", util.Title(tp.Name()))
+	for _, member := range structType.Members {
 		if member.IsInline {
-			var found = false
-			for _, ty := range types {
-				if strings.ToLower(ty.Name) == strings.ToLower(member.Name) {
-					found = true
-				}
-			}
-			if !found {
-				return errors.New("inline type " + member.Name + " not exist, please correct api file")
-			}
-			if _, err := fmt.Fprintf(writer, "%s\n", strings.Title(member.Type)); err != nil {
+			if _, err := fmt.Fprintf(writer, "%s\n", strings.Title(member.Type.Name())); err != nil {
 				return err
 			} else {
 				continue
 			}
 		}
-		tpString, err := convertTypeCase(types, member.Type)
-		if err != nil {
-			return err
-		}
-		pm, err := member.GetPropertyName()
-		if err != nil {
-			return err
-		}
-		if !strings.Contains(pm, "_") {
-			if strings.Title(member.Name) != strings.Title(pm) {
-				fmt.Printf("type: %s, property name %s json tag illegal, "+
-					"should set json tag as `json:\"%s\"` \n", tp.Name, member.Name, util.Untitle(member.Name))
-			}
-		}
-		if err := writeProperty(writer, member.Name, tpString, member.Tag, member.GetComment(), 1); err != nil {
+
+		if err := writeProperty(writer, member.Name, member.Tag, member.GetComment(), member.Type, 1); err != nil {
 			return err
 		}
 	}

+ 79 - 9
tools/goctl/api/gogen/util.go

@@ -72,15 +72,15 @@ func getParentPackage(dir string) (string, error) {
 	return filepath.ToSlash(filepath.Join(projectCtx.Path, strings.TrimPrefix(projectCtx.WorkDir, projectCtx.Dir))), nil
 }
 
-func writeProperty(writer io.Writer, name, tp, tag, comment string, indent int) error {
+func writeProperty(writer io.Writer, name, tag, comment string, tp spec.Type, indent int) error {
 	util.WriteIndent(writer, indent)
 	var err error
 	if len(comment) > 0 {
 		comment = strings.TrimPrefix(comment, "//")
 		comment = "//" + comment
-		_, err = fmt.Fprintf(writer, "%s %s %s %s\n", strings.Title(name), tp, tag, comment)
+		_, err = fmt.Fprintf(writer, "%s %s %s %s\n", strings.Title(name), tp.Name(), tag, comment)
 	} else {
-		_, err = fmt.Fprintf(writer, "%s %s %s\n", strings.Title(name), tp, tag)
+		_, err = fmt.Fprintf(writer, "%s %s %s\n", strings.Title(name), tp.Name(), tag)
 	}
 	return err
 }
@@ -88,11 +88,13 @@ func writeProperty(writer io.Writer, name, tp, tag, comment string, indent int)
 func getAuths(api *spec.ApiSpec) []string {
 	authNames := collection.NewSet()
 	for _, g := range api.Service.Groups {
-		if value, ok := util.GetAnnotationValue(g.Annotations, "server", "jwt"); ok {
-			authNames.Add(value)
+		jwt := g.GetAnnotation("jwt")
+		if len(jwt) > 0 {
+			authNames.Add(jwt)
 		}
-		if value, ok := util.GetAnnotationValue(g.Annotations, "server", "signature"); ok {
-			authNames.Add(value)
+		signature := g.GetAnnotation("signature")
+		if len(signature) > 0 {
+			authNames.Add(signature)
 		}
 	}
 	return authNames.KeysStr()
@@ -101,8 +103,9 @@ func getAuths(api *spec.ApiSpec) []string {
 func getMiddleware(api *spec.ApiSpec) []string {
 	result := collection.NewSet()
 	for _, g := range api.Service.Groups {
-		if value, ok := util.GetAnnotationValue(g.Annotations, "server", "middleware"); ok {
-			for _, item := range strings.Split(value, ",") {
+		middleware := g.GetAnnotation("middleware")
+		if len(middleware) > 0 {
+			for _, item := range strings.Split(middleware, ",") {
 				result.Add(strings.TrimSpace(item))
 			}
 		}
@@ -118,3 +121,70 @@ func formatCode(code string) string {
 
 	return string(ret)
 }
+
+func responseGoTypeName(r spec.Route, pkg ...string) string {
+	if r.ResponseType == nil {
+		return ""
+	}
+
+	return golangExpr(r.ResponseType, pkg...)
+}
+
+func requestGoTypeName(r spec.Route, pkg ...string) string {
+	if r.RequestType == nil {
+		return ""
+	}
+
+	return golangExpr(r.RequestType, pkg...)
+}
+
+func golangExpr(ty spec.Type, pkg ...string) string {
+	switch v := ty.(type) {
+	case spec.PrimitiveType:
+		return v.RawName
+	case spec.DefineStruct:
+		if len(pkg) > 1 {
+			panic("package cannot be more than 1")
+		}
+
+		if len(pkg) == 0 {
+			return v.RawName
+		}
+
+		return fmt.Sprintf("%s.%s", pkg[0], strings.Title(v.RawName))
+	case spec.ArrayType:
+		if len(pkg) > 1 {
+			panic("package cannot be more than 1")
+		}
+
+		if len(pkg) == 0 {
+			return v.RawName
+		}
+
+		return fmt.Sprintf("[]%s", golangExpr(v.Value, pkg...))
+	case spec.MapType:
+		if len(pkg) > 1 {
+			panic("package cannot be more than 1")
+		}
+
+		if len(pkg) == 0 {
+			return v.RawName
+		}
+
+		return fmt.Sprintf("map[%s]%s", v.Key, golangExpr(v.Value, pkg...))
+	case spec.PointerType:
+		if len(pkg) > 1 {
+			panic("package cannot be more than 1")
+		}
+
+		if len(pkg) == 0 {
+			return v.RawName
+		}
+
+		return fmt.Sprintf("*%s", golangExpr(v.Type, pkg...))
+	case spec.InterfaceType:
+		return v.RawName
+	}
+
+	return ""
+}

+ 1 - 5
tools/goctl/api/javagen/gen.go

@@ -22,11 +22,7 @@ func JavaCommand(c *cli.Context) error {
 		return errors.New("missing -dir")
 	}
 
-	p, err := parser.NewParser(apiFile)
-	if err != nil {
-		return err
-	}
-	api, err := p.Parse()
+	api, err := parser.Parse(apiFile)
 	if err != nil {
 		return err
 	}

+ 80 - 46
tools/goctl/api/javagen/gencomponents.go

@@ -8,6 +8,7 @@ import (
 	"strings"
 	"text/template"
 
+	"github.com/tal-tech/go-zero/core/stringx"
 	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
 	apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
 	"github.com/tal-tech/go-zero/tools/goctl/util"
@@ -17,21 +18,46 @@ const (
 	componentTemplate = `// Code generated by goctl. DO NOT EDIT.
 package com.xhb.logic.http.packet.{{.packet}}.model;
 
-import com.xhb.logic.http.DeProguardable;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+{{.imports}}
 
 {{.componentType}}
 `
+
+	httpResponseData = "import com.xhb.core.response.HttpResponseData;"
+	httpData         = "import com.xhb.core.packet.HttpData;"
 )
 
+type componentsContext struct {
+	api           *spec.ApiSpec
+	requestTypes  []spec.Type
+	responseTypes []spec.Type
+	imports       []string
+}
+
 func genComponents(dir, packetName string, api *spec.ApiSpec) error {
-	types := apiutil.GetSharedTypes(api)
+	types := api.Types
 	if len(types) == 0 {
 		return nil
 	}
+
+	var requestTypes []spec.Type
+	var responseTypes []spec.Type
+	for _, group := range api.Service.Groups {
+		for _, route := range group.Routes {
+			if route.RequestType != nil {
+				requestTypes = append(requestTypes, route.RequestType)
+			}
+			if route.ResponseType != nil {
+				responseTypes = append(responseTypes, route.ResponseType)
+			}
+		}
+	}
+
+	context := componentsContext{api: api, requestTypes: requestTypes, responseTypes: responseTypes}
 	for _, ty := range types {
-		if err := createComponent(dir, packetName, ty, api.Types); err != nil {
+		if err := context.createComponent(dir, packetName, ty); err != nil {
 			return err
 		}
 	}
@@ -39,8 +65,21 @@ func genComponents(dir, packetName string, api *spec.ApiSpec) error {
 	return nil
 }
 
-func createComponent(dir, packetName string, ty spec.Type, types []spec.Type) error {
-	modelFile := util.Title(ty.Name) + ".java"
+func (c *componentsContext) createComponent(dir, packetName string, ty spec.Type) error {
+	defineStruct, ok := ty.(spec.DefineStruct)
+	if !ok {
+		return errors.New("unsupported type %s" + ty.Name())
+	}
+
+	for _, item := range c.requestTypes {
+		if item.Name() == defineStruct.Name() {
+			if len(defineStruct.GetFormMembers())+len(defineStruct.GetBodyMembers()) == 0 {
+				return nil
+			}
+		}
+	}
+
+	modelFile := util.Title(ty.Name()) + ".java"
 	filename := path.Join(dir, modelDir, modelFile)
 	if err := util.RemoveOrQuit(filename); err != nil {
 		return err
@@ -55,77 +94,72 @@ func createComponent(dir, packetName string, ty spec.Type, types []spec.Type) er
 	}
 	defer fp.Close()
 
-	tys, err := buildType(ty, types)
+	tyString, err := c.buildType(defineStruct)
 	if err != nil {
 		return err
 	}
 
 	t := template.Must(template.New("componentType").Parse(componentTemplate))
 	return t.Execute(fp, map[string]string{
-		"componentType": tys,
+		"componentType": tyString,
 		"packet":        packetName,
+		"imports":       strings.Join(c.imports, "\n"),
 	})
 }
 
-func buildType(ty spec.Type, types []spec.Type) (string, error) {
+func (c *componentsContext) buildType(ty spec.DefineStruct) (string, error) {
 	var builder strings.Builder
-	if err := writeType(&builder, ty, types); err != nil {
-		return "", apiutil.WrapErr(err, "Type "+ty.Name+" generate error")
+	if err := c.writeType(&builder, ty); err != nil {
+		return "", apiutil.WrapErr(err, "Type "+ty.Name()+" generate error")
 	}
+
 	return builder.String(), nil
 }
 
-func writeType(writer io.Writer, tp spec.Type, types []spec.Type) error {
-	fmt.Fprintf(writer, "public class %s implements DeProguardable {\n", util.Title(tp.Name))
-	var members []spec.Member
-	err := writeMembers(writer, types, tp.Members, &members, 1)
+func (c *componentsContext) writeType(writer io.Writer, defineStruct spec.DefineStruct) error {
+	responseData := "HttpData"
+	for _, item := range c.responseTypes {
+		if item.Name() == defineStruct.Name() {
+			responseData = "HttpResponseData"
+			if !stringx.Contains(c.imports, httpResponseData) {
+				c.imports = append(c.imports, httpResponseData)
+			}
+			break
+		}
+	}
+	if responseData == "HttpData" && !stringx.Contains(c.imports, httpData) {
+		c.imports = append(c.imports, httpData)
+	}
+
+	fmt.Fprintf(writer, "public class %s extends %s {\n", util.Title(defineStruct.Name()), responseData)
+	err := c.writeMembers(writer, defineStruct, 1)
 	if err != nil {
 		return err
 	}
 
-	genGetSet(writer, members, 1)
+	genGetSet(writer, defineStruct, 1)
 	fmt.Fprintf(writer, "}")
 	return nil
 }
 
-func writeMembers(writer io.Writer, types []spec.Type, members []spec.Member, allMembers *[]spec.Member, indent int) error {
-	for _, member := range members {
-		if !member.IsInline {
-			_, err := member.GetPropertyName()
-			if err != nil {
-				return err
-			}
-		}
-		if !member.IsBodyMember() {
-			continue
-		}
-
-		for _, item := range *allMembers {
-			if item.Name == member.Name {
+func (c *componentsContext) writeMembers(writer io.Writer, ty spec.DefineStruct, indent int) error {
+	for _, member := range ty.Members {
+		if member.IsInline {
+			defineStruct, ok := member.Type.(spec.DefineStruct)
+			if ok {
+				err := c.writeMembers(writer, defineStruct, indent)
+				if err != nil {
+					return err
+				}
 				continue
 			}
+			return errors.New("unsupported inline type %s" + member.Type.Name())
 		}
 
-		if member.IsInline {
-			hasInline := false
-			for _, ty := range types {
-				if strings.ToLower(ty.Name) == strings.ToLower(member.Name) {
-					err := writeMembers(writer, types, ty.Members, allMembers, indent)
-					if err != nil {
-						return err
-					}
-					hasInline = true
-					break
-				}
-			}
-			if !hasInline {
-				return errors.New("inline type " + member.Name + " not exist, please correct api file")
-			}
-		} else {
+		if member.IsBodyMember() || member.IsFormMember() {
 			if err := writeProperty(writer, member, indent); err != nil {
 				return err
 			}
-			*allMembers = append(*allMembers, member)
 		}
 	}
 	return nil

+ 60 - 100
tools/goctl/api/javagen/genpacket.go

@@ -4,7 +4,6 @@ import (
 	"bufio"
 	"bytes"
 	"fmt"
-	"io"
 	"os"
 	"strings"
 	"text/template"
@@ -17,29 +16,16 @@ import (
 
 const packetTemplate = `package com.xhb.logic.http.packet.{{.packet}};
 
-import com.google.gson.Gson;
-import com.xhb.commons.JSON;
-import com.xhb.commons.JsonMarshal;
+import com.xhb.core.packet.HttpPacket;
 import com.xhb.core.network.HttpRequestClient;
-import com.xhb.core.packet.HttpRequestPacket;
-import com.xhb.core.response.HttpResponseData;
-import com.xhb.logic.http.DeProguardable;
-{{if not .HasRequestBody}}
-import com.xhb.logic.http.request.EmptyRequest;
-{{end}}
-{{.import}}
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.json.JSONObject;
-
-public class {{.packetName}} extends HttpRequestPacket<{{.packetName}}.{{.packetName}}Response> {
+{{.imports}}
 
+public class {{.packetName}} extends HttpPacket<{{.responseType}}> {
 	{{.paramsDeclaration}}
 
-	public {{.packetName}}({{.params}}{{if .HasRequestBody}}, {{.requestType}} request{{end}}) {
+	public {{.packetName}}({{.params}}{{if .HasRequestBody}}{{.requestType}} request{{end}}) {
 		{{if .HasRequestBody}}super(request);{{else}}super(EmptyRequest.instance);{{end}}
-		{{if .HasRequestBody}}this.request = request;{{end}}{{.paramsSet}}
+		{{if .HasRequestBody}}this.request = request;{{end}}{{.paramsSetter}}
     }
 
 	@Override
@@ -51,32 +37,6 @@ public class {{.packetName}} extends HttpRequestPacket<{{.packetName}}.{{.packet
     public String requestUri() {
         return {{.uri}};
     }
-
-	@Override
-    public {{.packetName}}Response newInstanceFrom(JSON json) {
-        return new {{.packetName}}Response(json);
-    }
-
-	public static class {{.packetName}}Response extends HttpResponseData {
-
-		private {{.responseType}} responseData;
-
-        {{.packetName}}Response(@NotNull JSON json) {
-            super(json);
-            JSONObject jsonObject = json.asObject();
-			if (JsonParser.hasKey(jsonObject, "data")) {
-				Gson gson = new Gson();
-				JSONObject dataJson = JsonParser.getJSONObject(jsonObject, "data");
-				responseData = gson.fromJson(dataJson.toString(), {{.responseType}}.class);
-			}
-        }
-
-		public {{.responseType}} get{{.responseType}} () {
-            return responseData;
-        }
-    }
-
-	{{.types}}
 }
 `
 
@@ -91,10 +51,11 @@ func genPacket(dir, packetName string, api *spec.ApiSpec) error {
 }
 
 func createWith(dir string, api *spec.ApiSpec, route spec.Route, packetName string) error {
-	packet, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
+	packet := route.Handler
 	packet = strings.Replace(packet, "Handler", "Packet", 1)
-	if !ok {
-		return fmt.Errorf("missing packet annotation for %q", route.Path)
+	packet = strings.Title(packet)
+	if !strings.HasSuffix(packet, "Packet") {
+		packet += "Packet"
 	}
 
 	javaFile := packet + ".java"
@@ -107,27 +68,24 @@ func createWith(dir string, api *spec.ApiSpec, route spec.Route, packetName stri
 	}
 	defer fp.Close()
 
-	var builder strings.Builder
-	var first bool
-	tps := apiutil.GetLocalTypes(api, route)
-
-	for _, tp := range tps {
-		if first {
-			first = false
-		} else {
-			fmt.Fprintln(&builder)
-		}
-
-		if err := genType(&builder, tp, api.Types); err != nil {
-			return err
+	var hasRequestBody = false
+	if route.RequestType != nil {
+		if defineStruct, ok := route.RequestType.(spec.DefineStruct); ok {
+			hasRequestBody = len(defineStruct.GetBodyMembers()) > 0 || len(defineStruct.GetFormMembers()) > 0
 		}
 	}
-	types := builder.String()
-	writeIndent(&builder, 1)
 
-	params := paramsForRoute(route)
+	params := strings.TrimSpace(paramsForRoute(route))
+	if len(params) > 0 && hasRequestBody {
+		params += ", "
+	}
 	paramsDeclaration := declarationForRoute(route)
-	paramsSet := paramsSet(route)
+	paramsSetter := paramsSet(route)
+	imports := getImports(api, packetName)
+
+	if len(route.ResponseTypeName()) == 0 {
+		imports += fmt.Sprintf("\v%s", "import com.xhb.core.response.EmptyResponse;")
+	}
 
 	t := template.Must(template.New("packetTemplate").Parse(packetTemplate))
 	var tmplBytes bytes.Buffer
@@ -135,15 +93,14 @@ func createWith(dir string, api *spec.ApiSpec, route spec.Route, packetName stri
 		"packetName":        packet,
 		"method":            strings.ToUpper(route.Method),
 		"uri":               processUri(route),
-		"types":             strings.TrimSpace(types),
-		"responseType":      stringx.TakeOne(util.Title(route.ResponseType.Name), "Object"),
+		"responseType":      stringx.TakeOne(util.Title(route.ResponseTypeName()), "EmptyResponse"),
 		"params":            params,
 		"paramsDeclaration": strings.TrimSpace(paramsDeclaration),
-		"paramsSet":         paramsSet,
+		"paramsSetter":      paramsSetter,
 		"packet":            packetName,
-		"requestType":       util.Title(route.RequestType.Name),
-		"HasRequestBody":    len(route.RequestType.GetBodyMembers()) > 0,
-		"import":            getImports(api, route, packetName),
+		"requestType":       util.Title(route.RequestTypeName()),
+		"HasRequestBody":    hasRequestBody,
+		"imports":           imports,
 	})
 	if err != nil {
 		return err
@@ -152,17 +109,11 @@ func createWith(dir string, api *spec.ApiSpec, route spec.Route, packetName stri
 	return nil
 }
 
-func getImports(api *spec.ApiSpec, route spec.Route, packetName string) string {
+func getImports(api *spec.ApiSpec, packetName string) string {
 	var builder strings.Builder
-	allTypes := apiutil.GetAllTypes(api, route)
-	sharedTypes := apiutil.GetSharedTypes(api)
-	for _, at := range allTypes {
-		for _, item := range sharedTypes {
-			if item.Name == at.Name {
-				fmt.Fprintf(&builder, "import com.xhb.logic.http.packet.%s.model.%s;\n", packetName, item.Name)
-				break
-			}
-		}
+	allTypes := api.Types
+	if len(allTypes) > 0 {
+		fmt.Fprintf(&builder, "import com.xhb.logic.http.packet.%s.model.*;\n", packetName)
 	}
 	return builder.String()
 }
@@ -241,6 +192,7 @@ func declarationForRoute(route spec.Route) string {
 
 func processUri(route spec.Route) string {
 	path := route.Path
+
 	var builder strings.Builder
 	cops := strings.Split(path, "/")
 	for index, cop := range cops {
@@ -261,29 +213,37 @@ func processUri(route spec.Route) string {
 		result = result[:len(result)-4]
 	}
 	if strings.HasPrefix(result, "/") {
+		result = strings.TrimPrefix(result, "/")
 		result = "\"" + result
 	}
-	return result
+	return result + formString(route)
 }
 
-func genType(writer io.Writer, tp spec.Type, types []spec.Type) error {
-	if len(tp.GetBodyMembers()) == 0 {
-		return nil
-	}
+func formString(route spec.Route) string {
+	var keyValues []string
+	if defineStruct, ok := route.RequestType.(spec.DefineStruct); ok {
+		forms := defineStruct.GetFormMembers()
+		for _, item := range forms {
+			name, err := item.GetPropertyName()
+			if err != nil {
+				panic(err)
+			}
 
-	writeIndent(writer, 1)
-	fmt.Fprintf(writer, "static class %s implements DeProguardable {\n", util.Title(tp.Name))
-	var members []spec.Member
-	err := writeMembers(writer, types, tp.Members, &members, 2)
-	if err != nil {
-		return err
+			strcat := "?"
+			if len(keyValues) > 0 {
+				strcat = "&"
+			}
+			if item.Type.Name() == "bool" {
+				name = strings.TrimPrefix(name, "Is")
+				name = strings.TrimPrefix(name, "is")
+				keyValues = append(keyValues, fmt.Sprintf(`"%s%s=" + request.is%s()`, strcat, name, strings.Title(name)))
+			} else {
+				keyValues = append(keyValues, fmt.Sprintf(`"%s%s=" + request.get%s()`, strcat, name, strings.Title(name)))
+			}
+		}
+		if len(keyValues) > 0 {
+			return " + " + strings.Join(keyValues, " + ")
+		}
 	}
-
-	writeNewline(writer)
-	writeIndent(writer, 1)
-	genGetSet(writer, members, 2)
-	writeIndent(writer, 1)
-	fmt.Fprintln(writer, "}")
-
-	return nil
+	return ""
 }

+ 90 - 64
tools/goctl/api/javagen/util.go

@@ -8,19 +8,30 @@ import (
 	"strings"
 	"text/template"
 
+	"github.com/tal-tech/go-zero/core/stringx"
 	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-	apiutil "github.com/tal-tech/go-zero/tools/goctl/api/util"
 	"github.com/tal-tech/go-zero/tools/goctl/util"
 )
 
 const getSetTemplate = `
 {{.indent}}{{.decorator}}
 {{.indent}}public {{.returnType}} get{{.property}}() {
-{{.indent}}	return this.{{.propertyValue}};
+{{.indent}}	return this.{{.tagValue}};
 {{.indent}}}
 
 {{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) {
-{{.indent}}	this.{{.propertyValue}} = {{.propertyValue}};
+{{.indent}}	this.{{.tagValue}} = {{.propertyValue}};
+{{.indent}}}
+`
+
+const boolTemplate = `
+{{.indent}}{{.decorator}}
+{{.indent}}public {{.returnType}} is{{.property}}() {
+{{.indent}}	return this.{{.tagValue}};
+{{.indent}}}
+
+{{.indent}}public void set{{.property}}({{.type}} {{.propertyValue}}) {
+{{.indent}}	this.{{.tagValue}} = {{.propertyValue}};
 {{.indent}}}
 `
 
@@ -31,22 +42,29 @@ func writeProperty(writer io.Writer, member spec.Member, indent int) error {
 	if err != nil {
 		return err
 	}
+
 	name, err := member.GetPropertyName()
 	if err != nil {
 		return err
 	}
+
 	_, err = fmt.Fprintf(writer, "private %s %s", ty, name)
 	if err != nil {
 		return err
 	}
+
 	writeDefaultValue(writer, member)
 	fmt.Fprint(writer, ";\n")
 	return err
 }
 
 func writeDefaultValue(writer io.Writer, member spec.Member) error {
-	switch member.Type {
-	case "string":
+	javaType, err := goTypeToJava(member.Type)
+	if err != nil {
+		return err
+	}
+
+	if javaType == "String" {
 		_, err := fmt.Fprintf(writer, " = \"\"")
 		return err
 	}
@@ -67,82 +85,84 @@ func indentString(indent int) string {
 	return result
 }
 
-func writeNewline(writer io.Writer) {
-	fmt.Fprint(writer, util.NL)
-}
-
-func isPrimitiveType(tp string) bool {
-	switch tp {
-	case "int", "int32", "int64":
-		return true
-	case "float", "float32", "float64":
-		return true
-	case "bool":
-		return true
-	}
-	return false
-}
-
-func goTypeToJava(tp string) (string, error) {
-	if len(tp) == 0 {
-		return "", errors.New("property type empty")
-	}
-
-	if strings.HasPrefix(tp, "*") {
-		tp = tp[1:]
-	}
-	switch tp {
-	case "string":
-		return "String", nil
-	case "int64":
-		return "long", nil
-	case "int", "int8", "int32":
-		return "int", nil
-	case "float", "float32", "float64":
-		return "double", nil
-	case "bool":
-		return "boolean", nil
-	}
-	if strings.HasPrefix(tp, "[]") {
-		tys, err := apiutil.DecomposeType(tp)
+func goTypeToJava(tp spec.Type) (string, error) {
+	switch v := tp.(type) {
+	case spec.DefineStruct:
+		return util.Title(tp.Name()), nil
+	case spec.PrimitiveType:
+		r, ok := primitiveType(tp.Name())
+		if !ok {
+			return "", errors.New("unsupported primitive type " + tp.Name())
+		}
+		return r, nil
+	case spec.MapType:
+		valueType, err := goTypeToJava(v.Value)
 		if err != nil {
 			return "", err
 		}
 
-		if len(tys) == 0 {
-			return "", fmt.Errorf("%s tp parse error", tp)
+		return fmt.Sprintf("java.util.HashMap<String, %s>", util.Title(valueType)), nil
+	case spec.ArrayType:
+		if tp.Name() == "[]byte" {
+			return "byte[]", nil
 		}
 
-		return fmt.Sprintf("java.util.ArrayList<%s>", util.Title(tys[0])), nil
-	} else if strings.HasPrefix(tp, "map") {
-		tys, err := apiutil.DecomposeType(tp)
+		valueType, err := goTypeToJava(v.Value)
 		if err != nil {
 			return "", err
 		}
 
-		if len(tys) == 2 {
-			return "", fmt.Errorf("%s tp parse error", tp)
-		}
+		return fmt.Sprintf("java.util.ArrayList<%s>", util.Title(valueType)), nil
+	case spec.InterfaceType:
+		return "Object", nil
+	case spec.PointerType:
+		return goTypeToJava(v.Type)
+	}
+
+	return "", errors.New("unsupported primitive type " + tp.Name())
+}
 
-		return fmt.Sprintf("java.util.HashMap<String, %s>", util.Title(tys[1])), nil
+func primitiveType(tp string) (string, bool) {
+	switch tp {
+	case "string":
+		return "String", true
+	case "int64", "uint64":
+		return "long", true
+	case "int", "int8", "int32", "uint", "uint8", "uint16", "uint32":
+		return "int", true
+	case "float", "float32", "float64":
+		return "double", true
+	case "bool":
+		return "boolean", true
 	}
-	return util.Title(tp), nil
+
+	return "", false
 }
 
-func genGetSet(writer io.Writer, members []spec.Member, indent int) error {
-	t := template.Must(template.New("getSetTemplate").Parse(getSetTemplate))
+func genGetSet(writer io.Writer, defineStruct spec.DefineStruct, indent int) error {
+	var members = defineStruct.GetBodyMembers()
+	members = append(members, defineStruct.GetFormMembers()...)
 	for _, member := range members {
-		var tmplBytes bytes.Buffer
-
-		oty, err := goTypeToJava(member.Type)
+		javaType, err := goTypeToJava(member.Type)
 		if err != nil {
-			return err
+			return nil
+		}
+
+		var property = util.Title(member.Name)
+		var templateStr = getSetTemplate
+		if javaType == "boolean" {
+			templateStr = boolTemplate
+			property = strings.TrimPrefix(property, "Is")
+			property = strings.TrimPrefix(property, "is")
 		}
+		t := template.Must(template.New(templateStr).Parse(getSetTemplate))
+		var tmplBytes bytes.Buffer
 
-		tyString := oty
+		tyString := javaType
 		decorator := ""
-		if !isPrimitiveType(member.Type) {
-			if member.IsOptional() {
+		javaPrimitiveType := []string{"int", "long", "boolean", "float", "double", "short"}
+		if !stringx.Contains(javaPrimitiveType, javaType) {
+			if member.IsOptional() || member.IsOmitEmpty() {
 				decorator = "@Nullable "
 			} else {
 				decorator = "@NotNull "
@@ -150,12 +170,18 @@ func genGetSet(writer io.Writer, members []spec.Member, indent int) error {
 			tyString = decorator + tyString
 		}
 
+		tagName, err := member.GetPropertyName()
+		if err != nil {
+			return err
+		}
+
 		err = t.Execute(&tmplBytes, map[string]string{
-			"property":      util.Title(member.Name),
+			"property":      property,
 			"propertyValue": util.Untitle(member.Name),
+			"tagValue":      tagName,
 			"type":          tyString,
 			"decorator":     decorator,
-			"returnType":    oty,
+			"returnType":    javaType,
 			"indent":        indentString(indent),
 		})
 		if err != nil {

+ 1 - 5
tools/goctl/api/ktgen/cmd.go

@@ -21,11 +21,7 @@ func KtCommand(c *cli.Context) error {
 		return errors.New("missing -pkg")
 	}
 
-	p, e := parser.NewParser(apiFile)
-	if e != nil {
-		return e
-	}
-	api, e := p.Parse()
+	api, e := parser.Parse(apiFile)
 	if e != nil {
 		return e
 	}

+ 43 - 1
tools/goctl/api/ktgen/funcs.go

@@ -1,6 +1,7 @@
 package ktgen
 
 import (
+	"fmt"
 	"log"
 	"strings"
 	"text/template"
@@ -44,7 +45,7 @@ func parseType(t string) string {
 	}
 
 	if strings.HasPrefix(t, "map") {
-		tys, e := util.DecomposeType(t)
+		tys, e := decomposeType(t)
 		if e != nil {
 			log.Fatal(e)
 		}
@@ -68,6 +69,47 @@ func parseType(t string) string {
 	}
 }
 
+func decomposeType(t string) (result []string, err error) {
+	add := func(tp string) error {
+		ret, err := decomposeType(tp)
+		if err != nil {
+			return err
+		}
+
+		result = append(result, ret...)
+		return nil
+	}
+	if strings.HasPrefix(t, "map") {
+		t = strings.ReplaceAll(t, "map", "")
+		if t[0] == '[' {
+			pos := strings.Index(t, "]")
+			if pos > 1 {
+				if err = add(t[1:pos]); err != nil {
+					return
+				}
+				if len(t) > pos+1 {
+					err = add(t[pos+1:])
+					return
+				}
+			}
+		}
+	} else if strings.HasPrefix(t, "[]") {
+		if len(t) > 2 {
+			err = add(t[2:])
+			return
+		}
+	} else if strings.HasPrefix(t, "*") {
+		err = add(t[1:])
+		return
+	} else {
+		result = append(result, t)
+		return
+	}
+
+	err = fmt.Errorf("bad type %q", t)
+	return
+}
+
 func add(a, i int) int {
 	return a + i
 }

+ 17 - 2
tools/goctl/api/ktgen/gen.go

@@ -126,10 +126,25 @@ func genBase(dir, pkg string, api *spec.ApiSpec) error {
 }
 
 func genApi(dir, pkg string, api *spec.ApiSpec) error {
-	name := strcase.ToCamel(api.Info.Title + "Api")
+	properties := api.Info.Properties
+	if properties == nil {
+		return fmt.Errorf("none properties")
+	}
+
+	title := properties["Title"]
+	if len(title) == 0 {
+		return fmt.Errorf("none title")
+	}
+
+	desc := properties["Desc"]
+	if len(desc) == 0 {
+		return fmt.Errorf("none desc")
+	}
+
+	name := strcase.ToCamel(title + "Api")
 	path := filepath.Join(dir, name+".kt")
 	api.Info.Title = name
-	api.Info.Desc = pkg
+	api.Info.Desc = desc
 
 	e := os.MkdirAll(dir, 0755)
 	if e != nil {

+ 0 - 268
tools/goctl/api/parser/apifileparser.go

@@ -1,268 +0,0 @@
-package parser
-
-import (
-	"bufio"
-	"bytes"
-	"errors"
-	"fmt"
-	"io"
-	"strings"
-
-	"github.com/tal-tech/go-zero/core/stringx"
-	"github.com/tal-tech/go-zero/tools/goctl/api/util"
-)
-
-const (
-	tokenInfo              = "info"
-	tokenImport            = "import"
-	tokenType              = "type"
-	tokenService           = "service"
-	tokenServiceAnnotation = "@server"
-	tokenStruct            = "struct"
-)
-
-type (
-	ApiStruct struct {
-		Info             string
-		Type             string
-		Service          string
-		Imports          string
-		serviceBeginLine int
-	}
-
-	apiFileState interface {
-		process(api *ApiStruct, token string) (apiFileState, error)
-	}
-
-	apiRootState struct {
-		*baseState
-	}
-
-	apiInfoState struct {
-		*baseState
-	}
-
-	apiImportState struct {
-		*baseState
-	}
-
-	apiTypeState struct {
-		*baseState
-	}
-
-	apiServiceState struct {
-		*baseState
-	}
-)
-
-func ParseApi(src string) (*ApiStruct, error) {
-	var buffer = new(bytes.Buffer)
-	buffer.WriteString(src)
-	api := new(ApiStruct)
-	var lineNumber = api.serviceBeginLine
-	apiFile := baseState{r: bufio.NewReader(buffer), lineNumber: &lineNumber}
-	st := apiRootState{&apiFile}
-	for {
-		st, err := st.process(api, "")
-		if err == io.EOF {
-			return api, nil
-		}
-		if err != nil {
-			return nil, fmt.Errorf("near line: %d, %s", lineNumber, err.Error())
-		}
-		if st == nil {
-			return api, nil
-		}
-	}
-}
-
-func (s *apiRootState) process(api *ApiStruct, _ string) (apiFileState, error) {
-	var builder strings.Builder
-	for {
-		ch, err := s.readSkipComment()
-		if err != nil {
-			return nil, err
-		}
-
-		switch {
-		case isSpace(ch) || isNewline(ch) || ch == leftParenthesis:
-			token := builder.String()
-			token = strings.TrimSpace(token)
-			if len(token) == 0 {
-				continue
-			}
-
-			builder.Reset()
-			switch token {
-			case tokenInfo:
-				info := apiInfoState{s.baseState}
-				return info.process(api, token+string(ch))
-			case tokenImport:
-				tp := apiImportState{s.baseState}
-				return tp.process(api, token+string(ch))
-			case tokenType:
-				ty := apiTypeState{s.baseState}
-				return ty.process(api, token+string(ch))
-			case tokenService:
-				server := apiServiceState{s.baseState}
-				return server.process(api, token+string(ch))
-			case tokenServiceAnnotation:
-				server := apiServiceState{s.baseState}
-				return server.process(api, token+string(ch))
-			default:
-				if strings.HasPrefix(token, "//") {
-					continue
-				}
-				return nil, errors.New(fmt.Sprintf("invalid token %s at line %d", token, *s.lineNumber))
-			}
-		default:
-			builder.WriteRune(ch)
-		}
-	}
-}
-
-func (s *apiInfoState) process(api *ApiStruct, token string) (apiFileState, error) {
-	for {
-		line, err := s.readLine()
-		if err != nil {
-			return nil, err
-		}
-
-		api.Info += newline + token + line
-		token = ""
-		if strings.TrimSpace(line) == string(rightParenthesis) {
-			return &apiRootState{s.baseState}, nil
-		}
-	}
-}
-
-func (s *apiImportState) process(api *ApiStruct, token string) (apiFileState, error) {
-	line, err := s.readLine()
-	if err != nil {
-		return nil, err
-	}
-
-	line = token + line
-	line = util.RemoveComment(line)
-	if len(strings.Fields(line)) != 2 {
-		return nil, errors.New("import syntax error: " + line)
-	}
-
-	api.Imports += newline + line
-	return &apiRootState{s.baseState}, nil
-}
-
-func (s *apiTypeState) process(api *ApiStruct, token string) (apiFileState, error) {
-	var blockCount = 0
-	var braceCount = 0
-	for {
-		line, err := s.readLine()
-		if err != nil {
-			return nil, err
-		}
-
-		line = token + line
-		if braceCount == 0 {
-			line = mayInsertStructKeyword(line)
-		}
-		api.Type += newline + newline + line
-		line = strings.TrimSpace(line)
-		line = util.RemoveComment(line)
-		token = ""
-
-		if strings.HasSuffix(line, leftBrace) {
-			blockCount++
-			braceCount++
-		}
-		if strings.HasSuffix(line, string(leftParenthesis)) {
-			blockCount++
-		}
-		if strings.HasSuffix(line, string(rightBrace)) {
-			blockCount--
-			braceCount--
-		}
-		if strings.HasSuffix(line, string(rightParenthesis)) {
-			blockCount--
-		}
-
-		if braceCount >= 2 {
-			return nil, errors.New("nested type not supported: " + line)
-		}
-		if braceCount < 0 {
-			line = strings.TrimSuffix(line, string(rightBrace))
-			line = strings.TrimSpace(line)
-			if strings.HasSuffix(line, leftBrace) {
-				blockCount++
-				braceCount++
-			}
-		}
-
-		if blockCount == 0 {
-			return &apiRootState{s.baseState}, nil
-		}
-	}
-}
-
-func (s *apiServiceState) process(api *ApiStruct, token string) (apiFileState, error) {
-	var blockCount = 0
-	for {
-		line, err := s.readLineSkipComment()
-		if err != nil {
-			return nil, err
-		}
-
-		line = token + line
-		token = ""
-		api.Service += newline + line
-		line = strings.TrimSpace(line)
-		line = util.RemoveComment(line)
-
-		if strings.HasSuffix(line, leftBrace) {
-			blockCount++
-		}
-		if strings.HasSuffix(line, string(leftParenthesis)) {
-			blockCount++
-		}
-		if line == string(rightBrace) {
-			blockCount--
-		}
-		if line == string(rightParenthesis) {
-			blockCount--
-		}
-
-		if blockCount == 0 {
-			return &apiRootState{s.baseState}, nil
-		}
-	}
-}
-
-func mayInsertStructKeyword(line string) string {
-	line = util.RemoveComment(line)
-	if !strings.HasSuffix(line, leftBrace) && !strings.HasSuffix(line, string(rightBrace)) {
-		return line
-	}
-
-	fields := strings.Fields(line)
-	if stringx.Contains(fields, tokenStruct) ||
-		stringx.Contains(fields, tokenStruct+leftBrace) ||
-		stringx.Contains(fields, tokenStruct+leftBrace+string(rightBrace)) ||
-		len(fields) <= 1 {
-		return line
-	}
-
-	var insertIndex int
-	if fields[0] == tokenType {
-		insertIndex = 2
-	} else {
-		insertIndex = 1
-	}
-	if insertIndex >= len(fields) {
-		return line
-	}
-
-	var result []string
-	result = append(result, fields[:insertIndex]...)
-	result = append(result, tokenStruct)
-	result = append(result, fields[insertIndex:]...)
-	return strings.Join(result, " ")
-}

+ 0 - 236
tools/goctl/api/parser/basestate.go

@@ -1,236 +0,0 @@
-package parser
-
-import (
-	"bufio"
-	"fmt"
-	"strings"
-)
-
-const (
-	startState = iota
-	attrNameState
-	attrValueState
-	attrColonState
-	multilineState
-)
-
-type baseState struct {
-	r          *bufio.Reader
-	lineNumber *int
-}
-
-func newBaseState(r *bufio.Reader, lineNumber *int) *baseState {
-	return &baseState{
-		r:          r,
-		lineNumber: lineNumber,
-	}
-}
-
-func (s *baseState) parseProperties() (map[string]string, error) {
-	var r = s.r
-	var attributes = make(map[string]string)
-	var builder strings.Builder
-	var key string
-	var st = startState
-
-	for {
-		ch, err := s.readSkipComment()
-		if err != nil {
-			return nil, err
-		}
-
-		switch st {
-		case startState:
-			switch {
-			case isNewline(ch):
-				return nil, fmt.Errorf("%q should be on the same line with %q", leftParenthesis, infoDirective)
-			case isSpace(ch):
-				continue
-			case ch == leftParenthesis:
-				st = attrNameState
-			default:
-				return nil, fmt.Errorf("unexpected char %q after %q", ch, infoDirective)
-			}
-		case attrNameState:
-			switch {
-			case isNewline(ch):
-				if builder.Len() > 0 {
-					return nil, fmt.Errorf("unexpected newline after %q", builder.String())
-				}
-			case isLetterDigit(ch):
-				builder.WriteRune(ch)
-			case isSpace(ch):
-				if builder.Len() > 0 {
-					key = builder.String()
-					builder.Reset()
-					st = attrColonState
-				}
-			case ch == colon:
-				if builder.Len() == 0 {
-					return nil, fmt.Errorf("unexpected leading %q", ch)
-				}
-				key = builder.String()
-				builder.Reset()
-				st = attrValueState
-			case ch == rightParenthesis:
-				return attributes, nil
-			}
-		case attrColonState:
-			switch {
-			case isSpace(ch):
-				continue
-			case ch == colon:
-				st = attrValueState
-			default:
-				return nil, fmt.Errorf("bad char %q after %q in %q", ch, key, infoDirective)
-			}
-		case attrValueState:
-			switch {
-			case ch == multilineBeginTag:
-				if builder.Len() > 0 {
-					return nil, fmt.Errorf("%q before %q", builder.String(), multilineBeginTag)
-				} else {
-					st = multilineState
-				}
-			case isSpace(ch):
-				if builder.Len() > 0 {
-					builder.WriteRune(ch)
-				}
-			case isNewline(ch):
-				attributes[key] = builder.String()
-				builder.Reset()
-				st = attrNameState
-			case ch == rightParenthesis:
-				attributes[key] = builder.String()
-				builder.Reset()
-				return attributes, nil
-			default:
-				builder.WriteRune(ch)
-			}
-		case multilineState:
-			switch {
-			case ch == multilineEndTag:
-				attributes[key] = builder.String()
-				builder.Reset()
-				st = attrNameState
-			case isNewline(ch):
-				var multipleNewlines bool
-			loopAfterNewline:
-				for {
-					next, err := read(r)
-					if err != nil {
-						return nil, err
-					}
-
-					switch {
-					case isSpace(next):
-						continue
-					case isNewline(next):
-						multipleNewlines = true
-					default:
-						if err := unread(r); err != nil {
-							return nil, err
-						}
-						break loopAfterNewline
-					}
-				}
-
-				if multipleNewlines {
-					fmt.Fprintln(&builder)
-				} else {
-					builder.WriteByte(' ')
-				}
-			case ch == rightParenthesis:
-				if builder.Len() > 0 {
-					attributes[key] = builder.String()
-					builder.Reset()
-				}
-				return attributes, nil
-			default:
-				builder.WriteRune(ch)
-			}
-		}
-	}
-}
-
-func (s *baseState) read() (rune, error) {
-	value, err := read(s.r)
-	if err != nil {
-		return 0, err
-	}
-	if isNewline(value) {
-		*s.lineNumber++
-	}
-	return value, nil
-}
-
-func (s *baseState) readSkipComment() (rune, error) {
-	ch, err := s.read()
-	if err != nil {
-		return 0, err
-	}
-
-	if isSlash(ch) {
-		value, err := s.mayReadToEndOfLine()
-		if err != nil {
-			return 0, err
-		}
-
-		if value > 0 {
-			ch = value
-		}
-	}
-	return ch, nil
-}
-
-func (s *baseState) mayReadToEndOfLine() (rune, error) {
-	ch, err := s.read()
-	if err != nil {
-		return 0, err
-	}
-
-	if isSlash(ch) {
-		for {
-			value, err := s.read()
-			if err != nil {
-				return 0, err
-			}
-
-			if isNewline(value) {
-				return value, nil
-			}
-		}
-	}
-	err = s.unread()
-	return 0, err
-}
-
-func (s *baseState) readLineSkipComment() (string, error) {
-	line, err := s.readLine()
-	if err != nil {
-		return "", err
-	}
-
-	var commentIdx = strings.Index(line, "//")
-	if commentIdx >= 0 {
-		return line[:commentIdx], nil
-	}
-	return line, nil
-}
-
-func (s *baseState) readLine() (string, error) {
-	line, _, err := s.r.ReadLine()
-	if err != nil {
-		return "", err
-	}
-	*s.lineNumber++
-	return string(line), nil
-}
-
-func (s *baseState) skipSpaces() error {
-	return skipSpaces(s.r)
-}
-
-func (s *baseState) unread() error {
-	return unread(s.r)
-}

+ 0 - 20
tools/goctl/api/parser/basestate_test.go

@@ -1,20 +0,0 @@
-package parser
-
-import (
-	"bufio"
-	"bytes"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestProperties(t *testing.T) {
-	const text = `(summary: hello world)`
-	var builder bytes.Buffer
-	builder.WriteString(text)
-	var lineNumber = 1
-	var state = newBaseState(bufio.NewReader(&builder), &lineNumber)
-	m, err := state.parseProperties()
-	assert.Nil(t, err)
-	assert.Equal(t, "hello world", m["summary"])
-}

+ 0 - 146
tools/goctl/api/parser/entity.go

@@ -1,146 +0,0 @@
-package parser
-
-import (
-	"errors"
-	"fmt"
-	"strings"
-
-	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-)
-
-type (
-	entity struct {
-		state  *baseState
-		api    *spec.ApiSpec
-		parser entityParser
-	}
-
-	entityParser interface {
-		parseLine(line string, api *spec.ApiSpec, annos []spec.Annotation) error
-		setEntityName(name string)
-	}
-)
-
-func newEntity(state *baseState, api *spec.ApiSpec, parser entityParser) entity {
-	return entity{
-		state:  state,
-		api:    api,
-		parser: parser,
-	}
-}
-
-func (s *entity) process() error {
-	line, err := s.state.readLineSkipComment()
-	if err != nil {
-		return err
-	}
-
-	fields := strings.Fields(line)
-	if len(fields) < 2 {
-		return fmt.Errorf("invalid type definition for %q",
-			strings.TrimSpace(strings.Trim(string(line), "{")))
-	}
-
-	if len(fields) == 2 {
-		if fields[1] != leftBrace {
-			return fmt.Errorf("bad string %q after type", fields[1])
-		}
-	} else if len(fields) == 3 {
-		if fields[1] != typeStruct {
-			return fmt.Errorf("bad string %q after type", fields[1])
-		}
-		if fields[2] != leftBrace {
-			return fmt.Errorf("bad string %q after type", fields[2])
-		}
-	}
-
-	s.parser.setEntityName(fields[0])
-
-	var annos []spec.Annotation
-memberLoop:
-	for {
-		ch, err := s.state.readSkipComment()
-		if err != nil {
-			return err
-		}
-
-		var annoName string
-		var builder strings.Builder
-		switch {
-		case ch == at:
-		annotationLoop:
-			for {
-				next, err := s.state.readSkipComment()
-				if err != nil {
-					return err
-				}
-				switch {
-				case isSpace(next):
-					if builder.Len() > 0 && annoName == "" {
-						annoName = builder.String()
-						builder.Reset()
-					}
-				case isNewline(next):
-					if builder.Len() == 0 {
-						return errors.New("invalid annotation format")
-					}
-
-					if len(annoName) > 0 {
-						value := builder.String()
-						if value != string(leftParenthesis) {
-							builder.Reset()
-							annos = append(annos, spec.Annotation{
-								Name:  annoName,
-								Value: value,
-							})
-							annoName = ""
-							break annotationLoop
-						}
-					}
-				case next == leftParenthesis:
-					if builder.Len() == 0 {
-						return errors.New("invalid annotation format")
-					}
-					annoName = builder.String()
-					builder.Reset()
-					if err := s.state.unread(); err != nil {
-						return err
-					}
-					attrs, err := s.state.parseProperties()
-					if err != nil {
-						return err
-					}
-					annos = append(annos, spec.Annotation{
-						Name:       annoName,
-						Properties: attrs,
-					})
-					annoName = ""
-					break annotationLoop
-				default:
-					builder.WriteRune(next)
-				}
-			}
-		case ch == rightBrace:
-			break memberLoop
-		case isLetterDigit(ch):
-			if err := s.state.unread(); err != nil {
-				return err
-			}
-
-			var line string
-			line, err = s.state.readLineSkipComment()
-			if err != nil {
-				return err
-			}
-
-			line = strings.TrimSpace(line)
-			if err := s.parser.parseLine(line, s.api, annos); err != nil {
-				return err
-			}
-
-			annos = nil
-		}
-	}
-
-	return nil
-}

+ 46 - 0
tools/goctl/api/parser/g4/ApiLexer.g4

@@ -0,0 +1,46 @@
+lexer grammar ApiLexer;
+
+// Keywords
+ATDOC:              '@doc';
+ATHANDLER:          '@handler';
+INTERFACE:          'interface{}';
+ATSERVER:           '@server';
+
+// Whitespace and comments
+WS:                 [ \t\r\n\u000C]+ -> channel(HIDDEN);
+COMMENT:            '/*' .*? '*/' -> channel(88);
+LINE_COMMENT:       '//' ~[\r\n]* -> channel(88);
+STRING:             '"' (~["\\] | EscapeSequence)* '"';
+RAW_STRING:         '`' (~[`\\\r\n] | EscapeSequence)+ '`';
+LINE_VALUE:         ':' [ \t]* (STRING|(~[\r\n"`]*));
+ID:         Letter LetterOrDigit*;
+
+
+fragment ExponentPart
+    : [eE] [+-]? Digits
+    ;
+
+fragment EscapeSequence
+    : '\\' [btnfr"'\\]
+    | '\\' ([0-3]? [0-7])? [0-7]
+    | '\\' 'u'+ HexDigit HexDigit HexDigit HexDigit
+    ;
+fragment HexDigits
+    : HexDigit ((HexDigit | '_')* HexDigit)?
+    ;
+fragment HexDigit
+    : [0-9a-fA-F]
+    ;
+fragment Digits
+    : [0-9] ([0-9_]* [0-9])?
+    ;
+
+fragment LetterOrDigit
+    : Letter
+    | [0-9]
+    ;
+fragment Letter
+    : [a-zA-Z$_] // these are the "java letters" below 0x7F
+    | ~[\u0000-\u007F\uD800-\uDBFF] // covers all characters above 0x7F which are not a surrogate
+    | [\uD800-\uDBFF] [\uDC00-\uDFFF] // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF
+    ;

+ 73 - 0
tools/goctl/api/parser/g4/ApiParser.g4

@@ -0,0 +1,73 @@
+grammar ApiParser;
+
+import ApiLexer;
+
+@lexer::members{
+    const COMEMNTS = 88
+}
+
+api:            spec*;
+spec:           syntaxLit
+                |importSpec
+                |infoSpec
+                |typeSpec
+                |serviceSpec
+                ;
+
+// syntax
+syntaxLit:      {match(p,"syntax")}syntaxToken=ID assign='=' {checkVersion(p)}version=STRING;
+
+// import
+importSpec:     importLit|importBlock;
+importLit:      {match(p,"import")}importToken=ID importValue ;
+importBlock:    {match(p,"import")}importToken=ID '(' importBlockValue+ ')';
+importBlockValue:   importValue;
+importValue:    {checkImportValue(p)}STRING;
+
+// info
+infoSpec:       {match(p,"info")}infoToken=ID lp='(' kvLit+ rp=')';
+
+// type
+typeSpec:       typeLit
+                |typeBlock;
+
+// eg: type Foo int
+typeLit:        {match(p,"type")}typeToken=ID  typeLitBody;
+// eg: type (...)
+typeBlock:      {match(p,"type")}typeToken=ID lp='(' typeBlockBody* rp=')';
+typeLitBody:    typeStruct|typeAlias;
+typeBlockBody:  typeBlockStruct|typeBlockAlias;
+typeStruct:     {checkKeyword(p)}structName=ID structToken=ID? lbrace='{'  field* rbrace='}';
+typeAlias:      {checkKeyword(p)}alias=ID assign='='? dataType;
+typeBlockStruct: {checkKeyword(p)}structName=ID structToken=ID? lbrace='{'  field* rbrace='}';
+typeBlockAlias: {checkKeyword(p)}alias=ID assign='='? dataType;
+field:          {isNormal(p)}? normalField|anonymousFiled ;
+normalField:    {checkKeyword(p)}fieldName=ID dataType tag=RAW_STRING?;
+anonymousFiled: star='*'? ID;
+dataType:       {isInterface(p)}ID
+                |mapType
+                |arrayType
+                |inter='interface{}'
+                |time='time.Time'
+                |pointerType
+                |typeStruct
+                ;
+pointerType:    star='*' {checkKeyword(p)}ID;
+mapType:        {match(p,"map")}mapToken=ID lbrack='[' {checkKey(p)}key=ID rbrack=']' value=dataType;
+arrayType:      lbrack='[' rbrack=']' dataType;
+
+// service
+serviceSpec:    atServer? serviceApi;
+atServer:       ATSERVER lp='(' kvLit+ rp=')';
+serviceApi:     {match(p,"service")}serviceToken=ID serviceName lbrace='{' serviceRoute* rbrace='}';
+serviceRoute:   atDoc? (atServer|atHandler) route;
+atDoc:          ATDOC lp='('? ((kvLit+)|STRING) rp=')'?;
+atHandler:      ATHANDLER ID;
+route:          {checkHttpMethod(p)}httpMethod=ID path request=body? returnToken=ID? response=replybody?;
+body:           lp='(' (ID)? rp=')';
+replybody:      lp='(' dataType? rp=')';
+// kv
+kvLit:          key=ID {checkKeyValue(p)}value=LINE_VALUE;
+
+serviceName:    (ID '-'?)+;
+path:           (('/' (ID ('-' ID)*))|('/:' (ID ('-' ID)?)))+;

+ 251 - 0
tools/goctl/api/parser/g4/ast/api.go

@@ -0,0 +1,251 @@
+package ast
+
+import (
+	"fmt"
+	"sort"
+
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+type Api struct {
+	LinePrefix string
+	Syntax     *SyntaxExpr
+	Import     []*ImportExpr
+	importM    map[string]PlaceHolder
+	Info       *InfoExpr
+	Type       []TypeExpr
+	typeM      map[string]PlaceHolder
+	Service    []*Service
+	serviceM   map[string]PlaceHolder
+	handlerM   map[string]PlaceHolder
+	routeM     map[string]PlaceHolder
+}
+
+func (v *ApiVisitor) VisitApi(ctx *api.ApiContext) interface{} {
+	defer func() {
+		if p := recover(); p != nil {
+			panic(fmt.Errorf("%+v", p))
+		}
+	}()
+
+	var final Api
+	final.importM = map[string]PlaceHolder{}
+	final.typeM = map[string]PlaceHolder{}
+	final.serviceM = map[string]PlaceHolder{}
+	final.handlerM = map[string]PlaceHolder{}
+	final.routeM = map[string]PlaceHolder{}
+	for _, each := range ctx.AllSpec() {
+		root := each.Accept(v).(*Api)
+		if root.Syntax != nil {
+			if final.Syntax != nil {
+				v.panic(root.Syntax.Syntax, fmt.Sprintf("mutiple syntax declaration"))
+			}
+
+			final.Syntax = root.Syntax
+		}
+
+		for _, imp := range root.Import {
+			if _, ok := final.importM[imp.Value.Text()]; ok {
+				v.panic(imp.Import, fmt.Sprintf("duplicate import '%s'", imp.Value.Text()))
+			}
+
+			final.importM[imp.Value.Text()] = Holder
+			final.Import = append(final.Import, imp)
+		}
+
+		if root.Info != nil {
+			infoM := map[string]PlaceHolder{}
+			if final.Info != nil {
+				v.panic(root.Info.Info, fmt.Sprintf("mutiple info declaration"))
+			}
+
+			for _, value := range root.Info.Kvs {
+				if _, ok := infoM[value.Key.Text()]; ok {
+					v.panic(value.Key, fmt.Sprintf("duplicate key '%s'", value.Key.Text()))
+				}
+				infoM[value.Key.Text()] = Holder
+			}
+
+			final.Info = root.Info
+		}
+
+		for _, tp := range root.Type {
+			if _, ok := final.typeM[tp.NameExpr().Text()]; ok {
+				v.panic(tp.NameExpr(), fmt.Sprintf("duplicate type '%s'", tp.NameExpr().Text()))
+			}
+
+			final.typeM[tp.NameExpr().Text()] = Holder
+			final.Type = append(final.Type, tp)
+		}
+
+		for _, service := range root.Service {
+			if _, ok := final.serviceM[service.ServiceApi.Name.Text()]; !ok && len(final.serviceM) > 0 {
+				v.panic(service.ServiceApi.Name, fmt.Sprintf("mutiple service declaration"))
+			}
+
+			if service.AtServer != nil {
+				atServerM := map[string]PlaceHolder{}
+				for _, kv := range service.AtServer.Kv {
+					if _, ok := atServerM[kv.Key.Text()]; ok {
+						v.panic(kv.Key, fmt.Sprintf("duplicate key '%s'", kv.Key.Text()))
+					}
+
+					atServerM[kv.Key.Text()] = Holder
+				}
+			}
+
+			for _, route := range service.ServiceApi.ServiceRoute {
+				uniqueRoute := fmt.Sprintf("%s %s", route.Route.Method.Text(), route.Route.Path.Text())
+				if _, ok := final.routeM[uniqueRoute]; ok {
+					v.panic(route.Route.Method, fmt.Sprintf("duplicate route '%s'", uniqueRoute))
+				}
+
+				final.routeM[uniqueRoute] = Holder
+				var handlerExpr Expr
+				if route.AtServer != nil {
+					atServerM := map[string]PlaceHolder{}
+					for _, kv := range route.AtServer.Kv {
+						if _, ok := atServerM[kv.Key.Text()]; ok {
+							v.panic(kv.Key, fmt.Sprintf("duplicate key '%s'", kv.Key.Text()))
+						}
+						atServerM[kv.Key.Text()] = Holder
+						if kv.Key.Text() == "handler" {
+							handlerExpr = kv.Value
+						}
+					}
+				}
+
+				if route.AtHandler != nil {
+					handlerExpr = route.AtHandler.Name
+				}
+
+				if handlerExpr == nil {
+					v.panic(route.Route.Method, fmt.Sprintf("mismtached handler"))
+				}
+
+				if handlerExpr.Text() == "" {
+					v.panic(handlerExpr, fmt.Sprintf("mismtached handler"))
+				}
+
+				if _, ok := final.handlerM[handlerExpr.Text()]; ok {
+					v.panic(handlerExpr, fmt.Sprintf("duplicate handler '%s'", handlerExpr.Text()))
+				}
+				final.handlerM[handlerExpr.Text()] = Holder
+			}
+			final.Service = append(final.Service, service)
+		}
+	}
+
+	return &final
+}
+
+func (v *ApiVisitor) VisitSpec(ctx *api.SpecContext) interface{} {
+	var root Api
+	if ctx.SyntaxLit() != nil {
+		root.Syntax = ctx.SyntaxLit().Accept(v).(*SyntaxExpr)
+	}
+
+	if ctx.ImportSpec() != nil {
+		root.Import = ctx.ImportSpec().Accept(v).([]*ImportExpr)
+	}
+
+	if ctx.InfoSpec() != nil {
+		root.Info = ctx.InfoSpec().Accept(v).(*InfoExpr)
+	}
+
+	if ctx.TypeSpec() != nil {
+		tp := ctx.TypeSpec().Accept(v)
+		root.Type = tp.([]TypeExpr)
+	}
+
+	if ctx.ServiceSpec() != nil {
+		root.Service = []*Service{ctx.ServiceSpec().Accept(v).(*Service)}
+	}
+
+	return &root
+}
+
+func (a *Api) Format() error {
+	// todo
+	return nil
+}
+
+func (a *Api) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	root, ok := v.(*Api)
+	if !ok {
+		return false
+	}
+
+	if !a.Syntax.Equal(root.Syntax) {
+		return false
+	}
+
+	if len(a.Import) != len(root.Import) {
+		return false
+	}
+
+	var expectingImport, actualImport []*ImportExpr
+	expectingImport = append(expectingImport, a.Import...)
+	actualImport = append(actualImport, root.Import...)
+
+	sort.Slice(expectingImport, func(i, j int) bool {
+		return expectingImport[i].Value.Text() < expectingImport[j].Value.Text()
+	})
+
+	sort.Slice(actualImport, func(i, j int) bool {
+		return actualImport[i].Value.Text() < actualImport[j].Value.Text()
+	})
+
+	for index, each := range expectingImport {
+		ac := actualImport[index]
+		if !each.Equal(ac) {
+			return false
+		}
+	}
+
+	if !a.Info.Equal(root.Info) {
+		return false
+	}
+
+	if len(a.Type) != len(root.Type) {
+		return false
+	}
+
+	var expectingType, actualType []TypeExpr
+	expectingType = append(expectingType, a.Type...)
+	actualType = append(actualType, root.Type...)
+
+	sort.Slice(expectingType, func(i, j int) bool {
+		return expectingType[i].NameExpr().Text() < expectingType[j].NameExpr().Text()
+	})
+	sort.Slice(actualType, func(i, j int) bool {
+		return actualType[i].NameExpr().Text() < actualType[j].NameExpr().Text()
+	})
+
+	for index, each := range expectingType {
+		ac := actualType[index]
+		if !each.Equal(ac) {
+			return false
+		}
+	}
+
+	if len(a.Service) != len(root.Service) {
+		return false
+	}
+
+	var expectingService, actualService []*Service
+	expectingService = append(expectingService, a.Service...)
+	actualService = append(actualService, root.Service...)
+	for index, each := range expectingService {
+		ac := actualService[index]
+		if !each.Equal(ac) {
+			return false
+		}
+	}
+
+	return true
+}

+ 405 - 0
tools/goctl/api/parser/g4/ast/apiparser.go

@@ -0,0 +1,405 @@
+package ast
+
+import (
+	"fmt"
+	"io/ioutil"
+	"path/filepath"
+	"strings"
+
+	"github.com/antlr/antlr4/runtime/Go/antlr"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+	"github.com/tal-tech/go-zero/tools/goctl/util/console"
+)
+
+type (
+	Parser struct {
+		linePrefix string
+		debug      bool
+		log        console.Console
+		antlr.DefaultErrorListener
+	}
+
+	ParserOption func(p *Parser)
+)
+
+func NewParser(options ...ParserOption) *Parser {
+	p := &Parser{
+		log: console.NewColorConsole(),
+	}
+	for _, opt := range options {
+		opt(p)
+	}
+
+	return p
+}
+
+// Accept can parse any terminalNode of api tree by fn.
+// -- for debug
+func (p *Parser) Accept(fn func(p *api.ApiParserParser, visitor *ApiVisitor) interface{}, content string) (v interface{}, err error) {
+	defer func() {
+		p := recover()
+		if p != nil {
+			switch e := p.(type) {
+			case error:
+				err = e
+			default:
+				err = fmt.Errorf("%+v", p)
+			}
+		}
+	}()
+
+	inputStream := antlr.NewInputStream(content)
+	lexer := api.NewApiParserLexer(inputStream)
+	lexer.RemoveErrorListeners()
+	tokens := antlr.NewCommonTokenStream(lexer, antlr.LexerDefaultTokenChannel)
+	apiParser := api.NewApiParserParser(tokens)
+	apiParser.RemoveErrorListeners()
+	apiParser.AddErrorListener(p)
+	var visitorOptions []VisitorOption
+	visitorOptions = append(visitorOptions, WithVisitorPrefix(p.linePrefix))
+	if p.debug {
+		visitorOptions = append(visitorOptions, WithVisitorDebug())
+	}
+	visitor := NewApiVisitor(visitorOptions...)
+	v = fn(apiParser, visitor)
+	return
+}
+
+// Parse is used to parse the api from the specified file name
+func (p *Parser) Parse(filename string) (*Api, error) {
+	data, err := p.readContent(filename)
+	if err != nil {
+		return nil, err
+	}
+
+	return p.parse(filename, data)
+}
+
+// ParseContent is used to parse the api from the specified content
+func (p *Parser) ParseContent(content string) (*Api, error) {
+	return p.parse("", content)
+}
+
+// parse is used to parse api from the content
+// filename is only used to mark the file where the error is located
+func (p *Parser) parse(filename, content string) (*Api, error) {
+	root, err := p.invoke(filename, content)
+	if err != nil {
+		return nil, err
+	}
+
+	var apiAstList []*Api
+	apiAstList = append(apiAstList, root)
+	for _, imp := range root.Import {
+		path := imp.Value.Text()
+		data, err := p.readContent(path)
+		if err != nil {
+			return nil, err
+		}
+
+		nestedApi, err := p.invoke(path, data)
+		if err != nil {
+			return nil, err
+		}
+
+		err = p.valid(root, nestedApi)
+		if err != nil {
+			return nil, err
+		}
+
+		apiAstList = append(apiAstList, nestedApi)
+	}
+
+	err = p.checkTypeDeclaration(apiAstList)
+	if err != nil {
+		return nil, err
+	}
+
+	allApi := p.memberFill(apiAstList)
+	return allApi, nil
+}
+
+func (p *Parser) invoke(linePrefix, content string) (v *Api, err error) {
+	defer func() {
+		p := recover()
+		if p != nil {
+			switch e := p.(type) {
+			case error:
+				err = e
+			default:
+				err = fmt.Errorf("%+v", p)
+			}
+		}
+	}()
+
+	if linePrefix != "" {
+		p.linePrefix = linePrefix
+	}
+
+	inputStream := antlr.NewInputStream(content)
+	lexer := api.NewApiParserLexer(inputStream)
+	lexer.RemoveErrorListeners()
+	tokens := antlr.NewCommonTokenStream(lexer, antlr.LexerDefaultTokenChannel)
+	apiParser := api.NewApiParserParser(tokens)
+	apiParser.RemoveErrorListeners()
+	apiParser.AddErrorListener(p)
+	var visitorOptions []VisitorOption
+	visitorOptions = append(visitorOptions, WithVisitorPrefix(p.linePrefix))
+	if p.debug {
+		visitorOptions = append(visitorOptions, WithVisitorDebug())
+	}
+
+	visitor := NewApiVisitor(visitorOptions...)
+	v = apiParser.Api().Accept(visitor).(*Api)
+	v.LinePrefix = p.linePrefix
+	return
+}
+
+func (p *Parser) valid(mainApi *Api, nestedApi *Api) error {
+	if len(nestedApi.Import) > 0 {
+		importToken := nestedApi.Import[0].Import
+		return fmt.Errorf("%s line %d:%d the nested api does not support import",
+			nestedApi.LinePrefix, importToken.Line(), importToken.Column())
+	}
+
+	if mainApi.Syntax != nil && nestedApi.Syntax != nil {
+		if mainApi.Syntax.Version.Text() != nestedApi.Syntax.Version.Text() {
+			syntaxToken := nestedApi.Syntax.Syntax
+			return fmt.Errorf("%s line %d:%d multiple syntax declaration, expecting syntax '%s', but found '%s'",
+				nestedApi.LinePrefix, syntaxToken.Line(), syntaxToken.Column(), mainApi.Syntax.Version.Text(), nestedApi.Syntax.Version.Text())
+		}
+	}
+
+	if len(mainApi.Service) > 0 {
+		mainService := mainApi.Service[0]
+		for _, service := range nestedApi.Service {
+			if mainService.ServiceApi.Name.Text() != service.ServiceApi.Name.Text() {
+				return fmt.Errorf("%s multiple service name declaration, expecting service name '%s', but found '%s'",
+					nestedApi.LinePrefix, mainService.ServiceApi.Name.Text(), service.ServiceApi.Name.Text())
+			}
+		}
+	}
+
+	mainHandlerMap := make(map[string]PlaceHolder)
+	mainRouteMap := make(map[string]PlaceHolder)
+	mainTypeMap := make(map[string]PlaceHolder)
+
+	routeMap := func(list []*ServiceRoute) (map[string]PlaceHolder, map[string]PlaceHolder) {
+		handlerMap := make(map[string]PlaceHolder)
+		routeMap := make(map[string]PlaceHolder)
+
+		for _, g := range list {
+			handler := g.GetHandler()
+			if handler.IsNotNil() {
+				var handlerName = handler.Text()
+				handlerMap[handlerName] = Holder
+				path := fmt.Sprintf("%s://%s", g.Route.Method.Text(), g.Route.Path.Text())
+				routeMap[path] = Holder
+			}
+		}
+
+		return handlerMap, routeMap
+	}
+
+	for _, each := range mainApi.Service {
+		h, r := routeMap(each.ServiceApi.ServiceRoute)
+
+		for k, v := range h {
+			mainHandlerMap[k] = v
+		}
+
+		for k, v := range r {
+			mainRouteMap[k] = v
+		}
+	}
+
+	for _, each := range mainApi.Type {
+		mainTypeMap[each.NameExpr().Text()] = Holder
+	}
+
+	// duplicate route check
+	for _, each := range nestedApi.Service {
+		for _, r := range each.ServiceApi.ServiceRoute {
+			handler := r.GetHandler()
+			if !handler.IsNotNil() {
+				return fmt.Errorf("%s handler not exist near line %d", nestedApi.LinePrefix, r.Route.Method.Line())
+			}
+
+			if _, ok := mainHandlerMap[handler.Text()]; ok {
+				return fmt.Errorf("%s line %d:%d duplicate handler '%s'",
+					nestedApi.LinePrefix, handler.Line(), handler.Column(), handler.Text())
+			}
+
+			path := fmt.Sprintf("%s://%s", r.Route.Method.Text(), r.Route.Path.Text())
+			if _, ok := mainRouteMap[path]; ok {
+				return fmt.Errorf("%s line %d:%d duplicate route '%s'",
+					nestedApi.LinePrefix, r.Route.Method.Line(), r.Route.Method.Column(), r.Route.Method.Text()+" "+r.Route.Path.Text())
+			}
+		}
+	}
+
+	// duplicate type check
+	for _, each := range nestedApi.Type {
+		if _, ok := mainTypeMap[each.NameExpr().Text()]; ok {
+			return fmt.Errorf("%s line %d:%d duplicate type declaration '%s'",
+				nestedApi.LinePrefix, each.NameExpr().Line(), each.NameExpr().Column(), each.NameExpr().Text())
+		}
+	}
+	return nil
+}
+
+func (p *Parser) memberFill(apiList []*Api) *Api {
+	var root Api
+	for index, each := range apiList {
+		if index == 0 {
+			root.Syntax = each.Syntax
+			root.Info = each.Info
+			root.Import = each.Import
+		}
+
+		root.Type = append(root.Type, each.Type...)
+		root.Service = append(root.Service, each.Service...)
+	}
+
+	return &root
+}
+
+// checkTypeDeclaration checks whether a struct type has been declared in context
+func (p *Parser) checkTypeDeclaration(apiList []*Api) error {
+	types := make(map[string]TypeExpr)
+
+	for _, root := range apiList {
+		for _, each := range root.Type {
+			types[each.NameExpr().Text()] = each
+		}
+	}
+
+	for _, apiItem := range apiList {
+		linePrefix := apiItem.LinePrefix
+		for _, each := range apiItem.Type {
+			tp, ok := each.(*TypeStruct)
+			if !ok {
+				continue
+			}
+
+			for _, member := range tp.Fields {
+				err := p.checkType(linePrefix, types, member.DataType)
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		for _, service := range apiItem.Service {
+			for _, each := range service.ServiceApi.ServiceRoute {
+				route := each.Route
+				if route.Req != nil && route.Req.Name.IsNotNil() && route.Req.Name.Expr().IsNotNil() {
+					_, ok := types[route.Req.Name.Expr().Text()]
+					if !ok {
+						return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
+							linePrefix, route.Req.Name.Expr().Line(), route.Req.Name.Expr().Column(), route.Req.Name.Expr().Text())
+					}
+				}
+
+				if route.Reply != nil && route.Reply.Name.IsNotNil() && route.Reply.Name.Expr().IsNotNil() {
+					reply := route.Reply.Name
+					var structName string
+					switch tp := reply.(type) {
+					case *Literal:
+						structName = tp.Literal.Text()
+					case *Array:
+						switch innerTp := tp.Literal.(type) {
+						case *Literal:
+							structName = innerTp.Literal.Text()
+						case *Pointer:
+							structName = innerTp.Name.Text()
+						}
+					}
+
+					if api.IsBasicType(structName) {
+						continue
+					}
+
+					_, ok := types[structName]
+					if !ok {
+						return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
+							linePrefix, route.Reply.Name.Expr().Line(), route.Reply.Name.Expr().Column(), structName)
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func (p *Parser) checkType(linePrefix string, types map[string]TypeExpr, expr DataType) error {
+	if expr == nil {
+		return nil
+	}
+
+	switch v := expr.(type) {
+	case *Literal:
+		name := v.Literal.Text()
+		if api.IsBasicType(name) {
+			return nil
+		}
+		_, ok := types[name]
+		if !ok {
+			return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
+				linePrefix, v.Literal.Line(), v.Literal.Column(), name)
+		}
+
+	case *Pointer:
+		name := v.Name.Text()
+		if api.IsBasicType(name) {
+			return nil
+		}
+		_, ok := types[name]
+		if !ok {
+			return fmt.Errorf("%s line %d:%d can not found declaration '%s' in context",
+				linePrefix, v.Name.Line(), v.Name.Column(), name)
+		}
+	case *Map:
+		return p.checkType(linePrefix, types, v.Value)
+	case *Array:
+		return p.checkType(linePrefix, types, v.Literal)
+	default:
+		return nil
+	}
+	return nil
+}
+
+func (p *Parser) readContent(filename string) (string, error) {
+	filename = strings.ReplaceAll(filename, `"`, "")
+	abs, err := filepath.Abs(filename)
+	if err != nil {
+		return "", err
+	}
+
+	data, err := ioutil.ReadFile(abs)
+	if err != nil {
+		return "", err
+	}
+
+	return string(data), nil
+}
+
+func (p *Parser) SyntaxError(_ antlr.Recognizer, _ interface{}, line, column int, msg string, _ antlr.RecognitionException) {
+	str := fmt.Sprintf(`%s line %d:%d  %s`, p.linePrefix, line, column, msg)
+	if p.debug {
+		p.log.Error(str)
+	}
+	panic(str)
+}
+
+func WithParserDebug() ParserOption {
+	return func(p *Parser) {
+		p.debug = true
+	}
+}
+
+func WithParserPrefix(prefix string) ParserOption {
+	return func(p *Parser) {
+		p.linePrefix = prefix
+	}
+}

+ 325 - 0
tools/goctl/api/parser/g4/ast/ast.go

@@ -0,0 +1,325 @@
+package ast
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"github.com/antlr/antlr4/runtime/Go/antlr"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+	"github.com/tal-tech/go-zero/tools/goctl/api/util"
+	"github.com/tal-tech/go-zero/tools/goctl/util/console"
+)
+
+type (
+	TokenStream interface {
+		GetStart() antlr.Token
+		GetStop() antlr.Token
+		GetParser() antlr.Parser
+	}
+	ApiVisitor struct {
+		api.BaseApiParserVisitor
+		debug    bool
+		log      console.Console
+		prefix   string
+		infoFlag bool
+	}
+
+	VisitorOption func(v *ApiVisitor)
+
+	Spec interface {
+		Doc() []Expr
+		Comment() Expr
+		Format() error
+		Equal(v interface{}) bool
+	}
+
+	Expr interface {
+		Prefix() string
+		Line() int
+		Column() int
+		Text() string
+		SetText(text string)
+		Start() int
+		Stop() int
+		Equal(expr Expr) bool
+		IsNotNil() bool
+	}
+)
+
+func NewApiVisitor(options ...VisitorOption) *ApiVisitor {
+	v := &ApiVisitor{
+		log: console.NewColorConsole(),
+	}
+	for _, opt := range options {
+		opt(v)
+	}
+	return v
+}
+
+func (v *ApiVisitor) panic(expr Expr, msg string) {
+	errString := fmt.Sprintf("%s line %d:%d  %s", v.prefix, expr.Line(), expr.Column(), msg)
+	if v.debug {
+		fmt.Println(errString)
+	}
+
+	panic(errString)
+}
+
+func WithVisitorPrefix(prefix string) VisitorOption {
+	return func(v *ApiVisitor) {
+		v.prefix = prefix
+	}
+}
+
+func WithVisitorDebug() VisitorOption {
+	return func(v *ApiVisitor) {
+		v.debug = true
+	}
+}
+
+type defaultExpr struct {
+	prefix, v    string
+	line, column int
+	start, stop  int
+}
+
+func NewTextExpr(v string) *defaultExpr {
+	return &defaultExpr{
+		v: v,
+	}
+}
+
+func (v *ApiVisitor) newExprWithTerminalNode(node antlr.TerminalNode) *defaultExpr {
+	if node == nil {
+		return nil
+	}
+	token := node.GetSymbol()
+	return v.newExprWithToken(token)
+}
+
+func (v *ApiVisitor) newExprWithToken(token antlr.Token) *defaultExpr {
+	if token == nil {
+		return nil
+	}
+
+	instance := &defaultExpr{}
+	instance.prefix = v.prefix
+	instance.v = token.GetText()
+	instance.line = token.GetLine()
+	instance.column = token.GetColumn()
+	instance.start = token.GetStart()
+	instance.stop = token.GetStop()
+
+	return instance
+}
+
+func (v *ApiVisitor) newExprWithText(text string, line, column, start, stop int) *defaultExpr {
+	instance := &defaultExpr{}
+	instance.prefix = v.prefix
+	instance.v = text
+	instance.line = line
+	instance.column = column
+	instance.start = start
+	instance.stop = stop
+	return instance
+}
+
+func (e *defaultExpr) Prefix() string {
+	if e == nil {
+		return ""
+	}
+
+	return e.prefix
+}
+
+func (e *defaultExpr) Line() int {
+	if e == nil {
+		return 0
+	}
+
+	return e.line
+}
+
+func (e *defaultExpr) Column() int {
+	if e == nil {
+		return 0
+	}
+
+	return e.column
+}
+
+func (e *defaultExpr) Text() string {
+	if e == nil {
+		return ""
+	}
+
+	return e.v
+}
+
+func (e *defaultExpr) SetText(text string) {
+	if e == nil {
+		return
+	}
+
+	e.v = text
+}
+
+func (e *defaultExpr) Start() int {
+	if e == nil {
+		return 0
+	}
+
+	return e.start
+}
+
+func (e *defaultExpr) Stop() int {
+	if e == nil {
+		return 0
+	}
+
+	return e.stop
+}
+
+func (e *defaultExpr) Equal(expr Expr) bool {
+	if e == nil {
+		if expr != nil {
+			return false
+		}
+
+		return true
+	}
+
+	if expr == nil {
+		return false
+	}
+
+	return e.v == expr.Text()
+}
+
+func (e *defaultExpr) IsNotNil() bool {
+	return e != nil
+}
+
+func EqualDoc(spec1, spec2 Spec) bool {
+	if spec1 == nil {
+		if spec2 != nil {
+			return false
+		}
+		return true
+	} else {
+		if spec2 == nil {
+			return false
+		}
+
+		var expectDoc, actualDoc []Expr
+		expectDoc = append(expectDoc, spec2.Doc()...)
+		actualDoc = append(actualDoc, spec1.Doc()...)
+		sort.Slice(expectDoc, func(i, j int) bool {
+			return expectDoc[i].Line() < expectDoc[j].Line()
+		})
+
+		for index, each := range actualDoc {
+			if !each.Equal(actualDoc[index]) {
+				return false
+			}
+		}
+
+		if spec1.Comment() != nil {
+			if spec2.Comment() == nil {
+				return false
+			}
+			if !spec1.Comment().Equal(spec2.Comment()) {
+				return false
+			}
+		} else {
+			if spec2.Comment() != nil {
+				return false
+			}
+		}
+	}
+	return true
+}
+
+func (v *ApiVisitor) getDoc(t TokenStream) []Expr {
+	list := v.getHiddenTokensToLeft(t, api.COMEMNTS, false)
+	return list
+}
+
+func (v *ApiVisitor) getComment(t TokenStream) Expr {
+	list := v.getHiddenTokensToRight(t, api.COMEMNTS)
+	if len(list) == 0 {
+		return nil
+	}
+
+	commentExpr := list[0]
+	stop := t.GetStop()
+	text := stop.GetText()
+	nlCount := strings.Count(text, "\n")
+
+	if commentExpr.Line() != stop.GetLine()+nlCount {
+		return nil
+	}
+
+	return commentExpr
+}
+
+func (v *ApiVisitor) getHiddenTokensToLeft(t TokenStream, channel int, containsCommentOfDefaultChannel bool) []Expr {
+	ct := t.GetParser().GetTokenStream().(*antlr.CommonTokenStream)
+	tokens := ct.GetHiddenTokensToLeft(t.GetStart().GetTokenIndex(), channel)
+	var tmp []antlr.Token
+	for _, each := range tokens {
+		tmp = append(tmp, each)
+	}
+
+	var list []Expr
+	for _, each := range tmp {
+		if !containsCommentOfDefaultChannel {
+			index := each.GetTokenIndex() - 1
+
+			if index > 0 {
+				allTokens := ct.GetAllTokens()
+				var flag = false
+				for i := index; i >= 0; i-- {
+					tk := allTokens[i]
+					if tk.GetChannel() == antlr.LexerDefaultTokenChannel {
+						if tk.GetLine() == each.GetLine() {
+							flag = true
+							break
+						}
+					}
+				}
+
+				if flag {
+					continue
+				}
+			}
+		}
+		list = append(list, v.newExprWithToken(each))
+	}
+	return list
+}
+
+func (v *ApiVisitor) getHiddenTokensToRight(t TokenStream, channel int) []Expr {
+	ct := t.GetParser().GetTokenStream().(*antlr.CommonTokenStream)
+	tokens := ct.GetHiddenTokensToRight(t.GetStop().GetTokenIndex(), channel)
+	var list []Expr
+	for _, each := range tokens {
+		list = append(list, v.newExprWithToken(each))
+	}
+	return list
+}
+
+func (v *ApiVisitor) exportCheck(expr Expr) {
+	if expr == nil || !expr.IsNotNil() {
+		return
+	}
+	if api.IsBasicType(expr.Text()) {
+		return
+	}
+
+	if util.UnExport(expr.Text()) {
+		v.log.Warning("%s line %d:%d unexported declaration '%s', use %s instead", expr.Prefix(), expr.Line(),
+			expr.Column(), expr.Text(), strings.Title(expr.Text()))
+	}
+}

+ 96 - 0
tools/goctl/api/parser/g4/ast/import.go

@@ -0,0 +1,96 @@
+package ast
+
+import (
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+type ImportExpr struct {
+	Import      Expr
+	Value       Expr
+	DocExpr     []Expr
+	CommentExpr Expr
+}
+
+func (v *ApiVisitor) VisitImportSpec(ctx *api.ImportSpecContext) interface{} {
+	var list []*ImportExpr
+	if ctx.ImportLit() != nil {
+		lits := ctx.ImportLit().Accept(v).([]*ImportExpr)
+		list = append(list, lits...)
+	}
+	if ctx.ImportBlock() != nil {
+		blocks := ctx.ImportBlock().Accept(v).([]*ImportExpr)
+		list = append(list, blocks...)
+	}
+
+	return list
+}
+
+func (v *ApiVisitor) VisitImportLit(ctx *api.ImportLitContext) interface{} {
+	importToken := v.newExprWithToken(ctx.GetImportToken())
+	valueExpr := ctx.ImportValue().Accept(v).(Expr)
+	return []*ImportExpr{
+		{
+			Import:      importToken,
+			Value:       valueExpr,
+			DocExpr:     v.getDoc(ctx),
+			CommentExpr: v.getComment(ctx),
+		},
+	}
+}
+
+func (v *ApiVisitor) VisitImportBlock(ctx *api.ImportBlockContext) interface{} {
+	importToken := v.newExprWithToken(ctx.GetImportToken())
+	values := ctx.AllImportBlockValue()
+	var list []*ImportExpr
+
+	for _, value := range values {
+		importExpr := value.Accept(v).(*ImportExpr)
+		importExpr.Import = importToken
+		list = append(list, importExpr)
+	}
+
+	return list
+}
+
+func (v *ApiVisitor) VisitImportBlockValue(ctx *api.ImportBlockValueContext) interface{} {
+	value := ctx.ImportValue().Accept(v).(Expr)
+	return &ImportExpr{
+		Value:       value,
+		DocExpr:     v.getDoc(ctx),
+		CommentExpr: v.getComment(ctx),
+	}
+}
+
+func (v *ApiVisitor) VisitImportValue(ctx *api.ImportValueContext) interface{} {
+	return v.newExprWithTerminalNode(ctx.STRING())
+}
+
+func (i *ImportExpr) Format() error {
+	// todo
+	return nil
+}
+
+func (i *ImportExpr) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	imp, ok := v.(*ImportExpr)
+	if !ok {
+		return false
+	}
+
+	if !EqualDoc(i, imp) {
+		return false
+	}
+
+	return i.Import.Equal(imp.Import) && i.Value.Equal(imp.Value)
+}
+
+func (i *ImportExpr) Doc() []Expr {
+	return i.DocExpr
+}
+
+func (i *ImportExpr) Comment() Expr {
+	return i.CommentExpr
+}

+ 67 - 0
tools/goctl/api/parser/g4/ast/info.go

@@ -0,0 +1,67 @@
+package ast
+
+import (
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+type InfoExpr struct {
+	Info Expr
+	Lp   Expr
+	Rp   Expr
+	Kvs  []*KvExpr
+}
+
+func (v *ApiVisitor) VisitInfoSpec(ctx *api.InfoSpecContext) interface{} {
+	var expr InfoExpr
+	expr.Info = v.newExprWithToken(ctx.GetInfoToken())
+	expr.Lp = v.newExprWithToken(ctx.GetLp())
+	expr.Rp = v.newExprWithToken(ctx.GetRp())
+	list := ctx.AllKvLit()
+	for _, each := range list {
+		kvExpr := each.Accept(v).(*KvExpr)
+		expr.Kvs = append(expr.Kvs, kvExpr)
+	}
+
+	if v.infoFlag {
+		v.panic(expr.Info, "duplicate declaration 'info'")
+	}
+
+	return &expr
+}
+
+func (i *InfoExpr) Format() error {
+	// todo
+	return nil
+}
+
+func (i *InfoExpr) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	info, ok := v.(*InfoExpr)
+	if !ok {
+		return false
+	}
+
+	if !i.Info.Equal(info.Info) {
+		return false
+	}
+
+	var expected, actual []*KvExpr
+	expected = append(expected, i.Kvs...)
+	actual = append(actual, info.Kvs...)
+
+	if len(expected) != len(actual) {
+		return false
+	}
+
+	for index, each := range expected {
+		ac := actual[index]
+		if !each.Equal(ac) {
+			return false
+		}
+	}
+
+	return true
+}

+ 79 - 0
tools/goctl/api/parser/g4/ast/kv.go

@@ -0,0 +1,79 @@
+package ast
+
+import (
+	"strings"
+
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+type KvExpr struct {
+	Key         Expr
+	Value       Expr
+	DocExpr     []Expr
+	CommentExpr Expr
+}
+
+func (v *ApiVisitor) VisitKvLit(ctx *api.KvLitContext) interface{} {
+	var kvExpr KvExpr
+	kvExpr.Key = v.newExprWithToken(ctx.GetKey())
+	commentExpr := v.getComment(ctx)
+	if ctx.GetValue() != nil {
+		valueText := ctx.GetValue().GetText()
+		valueExpr := v.newExprWithToken(ctx.GetValue())
+		if strings.Contains(valueText, "//") {
+			if commentExpr == nil {
+				commentExpr = v.newExprWithToken(ctx.GetValue())
+				commentExpr.SetText("")
+			}
+
+			index := strings.Index(valueText, "//")
+			commentExpr.SetText(valueText[index:])
+			valueExpr.SetText(strings.TrimSpace(valueText[:index]))
+		} else if strings.Contains(valueText, "/*") {
+			if commentExpr == nil {
+				commentExpr = v.newExprWithToken(ctx.GetValue())
+				commentExpr.SetText("")
+			}
+
+			index := strings.Index(valueText, "/*")
+			commentExpr.SetText(valueText[index:])
+			valueExpr.SetText(strings.TrimSpace(valueText[:index]))
+		}
+
+		kvExpr.Value = valueExpr
+	}
+
+	kvExpr.DocExpr = v.getDoc(ctx)
+	kvExpr.CommentExpr = commentExpr
+	return &kvExpr
+}
+
+func (k *KvExpr) Format() error {
+	// todo
+	return nil
+}
+
+func (k *KvExpr) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	kv, ok := v.(*KvExpr)
+	if !ok {
+		return false
+	}
+
+	if !EqualDoc(k, kv) {
+		return false
+	}
+
+	return k.Key.Equal(kv.Key) && k.Value.Equal(kv.Value)
+}
+
+func (k *KvExpr) Doc() []Expr {
+	return k.DocExpr
+}
+
+func (k *KvExpr) Comment() Expr {
+	return k.CommentExpr
+}

+ 5 - 0
tools/goctl/api/parser/g4/ast/placeholder.go

@@ -0,0 +1,5 @@
+package ast
+
+var Holder PlaceHolder
+
+type PlaceHolder struct{}

+ 603 - 0
tools/goctl/api/parser/g4/ast/service.go

@@ -0,0 +1,603 @@
+package ast
+
+import (
+	"fmt"
+	"sort"
+
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+type Service struct {
+	AtServer   *AtServer
+	ServiceApi *ServiceApi
+}
+
+type KV []*KvExpr
+
+type AtServer struct {
+	AtServerToken Expr
+	Lp            Expr
+	Rp            Expr
+	Kv            KV
+}
+
+type ServiceApi struct {
+	ServiceToken Expr
+	Name         Expr
+	Lbrace       Expr
+	Rbrace       Expr
+	ServiceRoute []*ServiceRoute
+}
+
+type ServiceRoute struct {
+	AtDoc     *AtDoc
+	AtServer  *AtServer
+	AtHandler *AtHandler
+	Route     *Route
+}
+
+type AtDoc struct {
+	AtDocToken Expr
+	Lp         Expr
+	Rp         Expr
+	LineDoc    Expr
+	Kv         []*KvExpr
+}
+
+type AtHandler struct {
+	AtHandlerToken Expr
+	Name           Expr
+	DocExpr        []Expr
+	CommentExpr    Expr
+}
+
+type Route struct {
+	Method      Expr
+	Path        Expr
+	Req         *Body
+	ReturnToken Expr
+	Reply       *Body
+	DocExpr     []Expr
+	CommentExpr Expr
+}
+
+type Body struct {
+	Lp   Expr
+	Rp   Expr
+	Name DataType
+}
+
+func (v *ApiVisitor) VisitServiceSpec(ctx *api.ServiceSpecContext) interface{} {
+	var serviceSpec Service
+	if ctx.AtServer() != nil {
+		serviceSpec.AtServer = ctx.AtServer().Accept(v).(*AtServer)
+	}
+
+	serviceSpec.ServiceApi = ctx.ServiceApi().Accept(v).(*ServiceApi)
+	return &serviceSpec
+}
+
+func (v *ApiVisitor) VisitAtServer(ctx *api.AtServerContext) interface{} {
+	var atServer AtServer
+	atServer.AtServerToken = v.newExprWithTerminalNode(ctx.ATSERVER())
+	atServer.Lp = v.newExprWithToken(ctx.GetLp())
+	atServer.Rp = v.newExprWithToken(ctx.GetRp())
+
+	for _, each := range ctx.AllKvLit() {
+		atServer.Kv = append(atServer.Kv, each.Accept(v).(*KvExpr))
+	}
+
+	return &atServer
+}
+
+func (v *ApiVisitor) VisitServiceApi(ctx *api.ServiceApiContext) interface{} {
+	var serviceApi ServiceApi
+	serviceApi.ServiceToken = v.newExprWithToken(ctx.GetServiceToken())
+	serviceName := ctx.ServiceName()
+	serviceApi.Name = v.newExprWithText(serviceName.GetText(), serviceName.GetStart().GetLine(), serviceName.GetStart().GetColumn(), serviceName.GetStart().GetStart(), serviceName.GetStop().GetStop())
+	serviceApi.Lbrace = v.newExprWithToken(ctx.GetLbrace())
+	serviceApi.Rbrace = v.newExprWithToken(ctx.GetRbrace())
+
+	for _, each := range ctx.AllServiceRoute() {
+		serviceApi.ServiceRoute = append(serviceApi.ServiceRoute, each.Accept(v).(*ServiceRoute))
+	}
+
+	return &serviceApi
+}
+
+func (v *ApiVisitor) VisitServiceRoute(ctx *api.ServiceRouteContext) interface{} {
+	var serviceRoute ServiceRoute
+	if ctx.AtDoc() != nil {
+		serviceRoute.AtDoc = ctx.AtDoc().Accept(v).(*AtDoc)
+	}
+
+	if ctx.AtServer() != nil {
+		serviceRoute.AtServer = ctx.AtServer().Accept(v).(*AtServer)
+	} else if ctx.AtHandler() != nil {
+		serviceRoute.AtHandler = ctx.AtHandler().Accept(v).(*AtHandler)
+	}
+
+	serviceRoute.Route = ctx.Route().Accept(v).(*Route)
+	return &serviceRoute
+}
+
+func (v *ApiVisitor) VisitAtDoc(ctx *api.AtDocContext) interface{} {
+	var atDoc AtDoc
+	atDoc.AtDocToken = v.newExprWithTerminalNode(ctx.ATDOC())
+
+	if ctx.STRING() != nil {
+		atDoc.LineDoc = v.newExprWithTerminalNode(ctx.STRING())
+	} else {
+		for _, each := range ctx.AllKvLit() {
+			atDoc.Kv = append(atDoc.Kv, each.Accept(v).(*KvExpr))
+		}
+	}
+	atDoc.Lp = v.newExprWithToken(ctx.GetLp())
+	atDoc.Rp = v.newExprWithToken(ctx.GetRp())
+
+	if ctx.GetLp() != nil {
+		if ctx.GetRp() == nil {
+			v.panic(atDoc.Lp, "mismatched ')'")
+		}
+	}
+
+	if ctx.GetRp() != nil {
+		if ctx.GetLp() == nil {
+			v.panic(atDoc.Rp, "mismatched '('")
+		}
+	}
+
+	return &atDoc
+}
+
+func (v *ApiVisitor) VisitAtHandler(ctx *api.AtHandlerContext) interface{} {
+	var atHandler AtHandler
+	astHandlerExpr := v.newExprWithTerminalNode(ctx.ATHANDLER())
+	atHandler.AtHandlerToken = astHandlerExpr
+	atHandler.Name = v.newExprWithTerminalNode(ctx.ID())
+	atHandler.DocExpr = v.getDoc(ctx)
+	atHandler.CommentExpr = v.getComment(ctx)
+	return &atHandler
+}
+
+func (v *ApiVisitor) VisitRoute(ctx *api.RouteContext) interface{} {
+	var route Route
+	path := ctx.Path()
+	methodExpr := v.newExprWithToken(ctx.GetHttpMethod())
+	route.Method = methodExpr
+	route.Path = v.newExprWithText(path.GetText(), path.GetStart().GetLine(), path.GetStart().GetColumn(), path.GetStart().GetStart(), path.GetStop().GetStop())
+
+	if ctx.GetRequest() != nil {
+		req := ctx.GetRequest().Accept(v)
+		if req != nil {
+			route.Req = req.(*Body)
+		}
+	}
+
+	if ctx.GetResponse() != nil {
+		reply := ctx.GetResponse().Accept(v)
+		if reply != nil {
+			route.Reply = reply.(*Body)
+		}
+	}
+	if ctx.GetReturnToken() != nil {
+		returnExpr := v.newExprWithToken(ctx.GetReturnToken())
+		if ctx.GetReturnToken().GetText() != "returns" {
+			v.panic(returnExpr, fmt.Sprintf("expecting returns, found input '%s'", ctx.GetReturnToken().GetText()))
+		}
+		route.ReturnToken = returnExpr
+	}
+
+	route.DocExpr = v.getDoc(ctx)
+	route.CommentExpr = v.getComment(ctx)
+	return &route
+}
+
+func (v *ApiVisitor) VisitBody(ctx *api.BodyContext) interface{} {
+	if ctx.ID() == nil {
+		return nil
+	}
+
+	idRxpr := v.newExprWithTerminalNode(ctx.ID())
+	if api.IsGolangKeyWord(idRxpr.Text()) {
+		v.panic(idRxpr, fmt.Sprintf("expecting 'ID', but found golang keyword '%s'", idRxpr.Text()))
+	}
+	v.exportCheck(idRxpr)
+
+	return &Body{
+		Lp:   v.newExprWithToken(ctx.GetLp()),
+		Rp:   v.newExprWithToken(ctx.GetRp()),
+		Name: &Literal{Literal: idRxpr},
+	}
+}
+
+// note: forward compatible
+func (v *ApiVisitor) VisitReplybody(ctx *api.ReplybodyContext) interface{} {
+	if ctx.DataType() == nil {
+		return nil
+	}
+
+	dt := ctx.DataType().Accept(v).(DataType)
+	if dt == nil {
+		return nil
+	}
+
+	switch dataType := dt.(type) {
+	case *Array:
+		lit := dataType.Literal
+		switch lit.(type) {
+		case *Literal, *Pointer:
+			if api.IsGolangKeyWord(lit.Expr().Text()) {
+				v.panic(lit.Expr(), fmt.Sprintf("expecting 'ID', but found golang keyword '%s'", lit.Expr().Text()))
+			}
+		default:
+			v.panic(dt.Expr(), fmt.Sprintf("unsupport %s", dt.Expr().Text()))
+		}
+		v.log.Warning("%s %d:%d deprecated array type near '%s'", v.prefix, dataType.ArrayExpr.Line(), dataType.ArrayExpr.Column(), dataType.ArrayExpr.Text())
+	case *Literal:
+		lit := dataType.Literal.Text()
+		if api.IsGolangKeyWord(dataType.Literal.Text()) {
+			v.panic(dataType.Literal, fmt.Sprintf("expecting 'ID', but found golang keyword '%s'", dataType.Literal.Text()))
+		}
+		if api.IsBasicType(lit) {
+			v.panic(dt.Expr(), fmt.Sprintf("unsupport %s", dt.Expr().Text()))
+		}
+	default:
+		v.panic(dt.Expr(), fmt.Sprintf("unsupport %s", dt.Expr().Text()))
+	}
+
+	return &Body{
+		Lp:   v.newExprWithToken(ctx.GetLp()),
+		Rp:   v.newExprWithToken(ctx.GetRp()),
+		Name: dt,
+	}
+}
+
+func (b *Body) Format() error {
+	// todo
+	return nil
+}
+func (b *Body) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	body, ok := v.(*Body)
+	if !ok {
+		return false
+	}
+
+	if !b.Lp.Equal(body.Lp) {
+		return false
+	}
+
+	if !b.Rp.Equal(body.Rp) {
+		return false
+	}
+
+	return b.Name.Equal(body.Name)
+}
+
+func (r *Route) Format() error {
+	// todo
+	return nil
+}
+
+func (r *Route) Doc() []Expr {
+	return r.DocExpr
+}
+
+func (r *Route) Comment() Expr {
+	return r.CommentExpr
+}
+
+func (r *Route) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	route, ok := v.(*Route)
+	if !ok {
+		return false
+	}
+
+	if !r.Method.Equal(route.Method) {
+		return false
+	}
+
+	if !r.Path.Equal(route.Path) {
+		return false
+	}
+
+	if r.Req != nil {
+		if !r.Req.Equal(route.Req) {
+			return false
+		}
+	}
+
+	if r.ReturnToken != nil {
+		if !r.ReturnToken.Equal(route.ReturnToken) {
+			return false
+		}
+	}
+
+	if r.Reply != nil {
+		if !r.Reply.Equal(route.Reply) {
+			return false
+		}
+	}
+
+	return EqualDoc(r, route)
+}
+
+func (a *AtHandler) Doc() []Expr {
+	return a.DocExpr
+}
+
+func (a *AtHandler) Comment() Expr {
+	return a.CommentExpr
+}
+
+func (a *AtHandler) Format() error {
+	// todo
+	return nil
+}
+
+func (a *AtHandler) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	atHandler, ok := v.(*AtHandler)
+	if !ok {
+		return false
+	}
+
+	if !a.AtHandlerToken.Equal(atHandler.AtHandlerToken) {
+		return false
+	}
+
+	if !a.Name.Equal(atHandler.Name) {
+		return false
+	}
+
+	return EqualDoc(a, atHandler)
+}
+
+func (a *AtDoc) Format() error {
+	// todo
+	return nil
+}
+
+func (a *AtDoc) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	atDoc, ok := v.(*AtDoc)
+	if !ok {
+		return false
+	}
+
+	if !a.AtDocToken.Equal(atDoc.AtDocToken) {
+		return false
+	}
+
+	if a.Lp.IsNotNil() {
+		if !a.Lp.Equal(atDoc.Lp) {
+			return false
+		}
+	}
+
+	if a.Rp.IsNotNil() {
+		if !a.Rp.Equal(atDoc.Rp) {
+			return false
+		}
+	}
+
+	if a.LineDoc != nil {
+		if !a.LineDoc.Equal(atDoc.LineDoc) {
+			return false
+		}
+	}
+
+	var expecting, actual []*KvExpr
+	expecting = append(expecting, a.Kv...)
+	actual = append(actual, atDoc.Kv...)
+
+	if len(expecting) != len(actual) {
+		return false
+	}
+
+	for index, each := range expecting {
+		ac := actual[index]
+		if !each.Equal(ac) {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (a *AtServer) Format() error {
+	// todo
+	return nil
+}
+
+func (a *AtServer) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	atServer, ok := v.(*AtServer)
+	if !ok {
+		return false
+	}
+
+	if !a.AtServerToken.Equal(atServer.AtServerToken) {
+		return false
+	}
+
+	if !a.Lp.Equal(atServer.Lp) {
+		return false
+	}
+
+	if !a.Rp.Equal(atServer.Rp) {
+		return false
+	}
+
+	var expecting, actual []*KvExpr
+	expecting = append(expecting, a.Kv...)
+	actual = append(actual, atServer.Kv...)
+	if len(expecting) != len(actual) {
+		return false
+	}
+
+	sort.Slice(expecting, func(i, j int) bool {
+		return expecting[i].Key.Text() < expecting[j].Key.Text()
+	})
+
+	sort.Slice(actual, func(i, j int) bool {
+		return actual[i].Key.Text() < actual[j].Key.Text()
+	})
+
+	for index, each := range expecting {
+		ac := actual[index]
+		if !each.Equal(ac) {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (s *ServiceRoute) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	sr, ok := v.(*ServiceRoute)
+	if !ok {
+		return false
+	}
+
+	if !s.AtDoc.Equal(sr.AtDoc) {
+		return false
+	}
+
+	if s.AtServer != nil {
+		if !s.AtServer.Equal(sr.AtServer) {
+			return false
+		}
+	}
+
+	if s.AtHandler != nil {
+		if !s.AtHandler.Equal(sr.AtHandler) {
+			return false
+		}
+	}
+
+	return s.Route.Equal(sr.Route)
+}
+
+func (s *ServiceRoute) Format() error {
+	// todo
+	return nil
+}
+
+func (s *ServiceRoute) GetHandler() Expr {
+	if s.AtHandler != nil {
+		return s.AtHandler.Name
+	} else {
+		return s.AtServer.Kv.Get("handler")
+	}
+}
+
+func (a *ServiceApi) Format() error {
+	// todo
+	return nil
+}
+
+func (a *ServiceApi) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	api, ok := v.(*ServiceApi)
+	if !ok {
+		return false
+	}
+
+	if !a.ServiceToken.Equal(api.ServiceToken) {
+		return false
+	}
+
+	if !a.Name.Equal(api.Name) {
+		return false
+	}
+
+	if !a.Lbrace.Equal(api.Lbrace) {
+		return false
+	}
+
+	if !a.Rbrace.Equal(api.Rbrace) {
+		return false
+	}
+
+	var expecting, acutal []*ServiceRoute
+	expecting = append(expecting, a.ServiceRoute...)
+	acutal = append(acutal, api.ServiceRoute...)
+	if len(expecting) != len(acutal) {
+		return false
+	}
+
+	sort.Slice(expecting, func(i, j int) bool {
+		return expecting[i].Route.Path.Text() < expecting[j].Route.Path.Text()
+	})
+
+	sort.Slice(acutal, func(i, j int) bool {
+		return acutal[i].Route.Path.Text() < acutal[j].Route.Path.Text()
+	})
+
+	for index, each := range expecting {
+		ac := acutal[index]
+		if !each.Equal(ac) {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (s *Service) Format() error {
+	// todo
+	return nil
+}
+
+func (s *Service) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	service, ok := v.(*Service)
+	if !ok {
+		return false
+	}
+
+	if s.AtServer != nil {
+		if !s.AtServer.Equal(service.AtServer) {
+			return false
+		}
+	}
+
+	return s.ServiceApi.Equal(service.ServiceApi)
+}
+
+func (kv KV) Get(key string) Expr {
+	for _, each := range kv {
+		if each.Key.Text() == key {
+			return each.Value
+		}
+	}
+	return nil
+}

+ 58 - 0
tools/goctl/api/parser/g4/ast/syntax.go

@@ -0,0 +1,58 @@
+package ast
+
+import (
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+type SyntaxExpr struct {
+	Syntax      Expr
+	Assign      Expr
+	Version     Expr
+	DocExpr     []Expr
+	CommentExpr Expr
+}
+
+func (v *ApiVisitor) VisitSyntaxLit(ctx *api.SyntaxLitContext) interface{} {
+	syntax := v.newExprWithToken(ctx.GetSyntaxToken())
+	assign := v.newExprWithToken(ctx.GetAssign())
+	version := v.newExprWithToken(ctx.GetVersion())
+	return &SyntaxExpr{
+		Syntax:      syntax,
+		Assign:      assign,
+		Version:     version,
+		DocExpr:     v.getDoc(ctx),
+		CommentExpr: v.getComment(ctx),
+	}
+}
+
+func (s *SyntaxExpr) Format() error {
+	// todo
+	return nil
+}
+
+func (s *SyntaxExpr) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	syntax, ok := v.(*SyntaxExpr)
+	if !ok {
+		return false
+	}
+
+	if !EqualDoc(s, syntax) {
+		return false
+	}
+
+	return s.Syntax.Equal(syntax.Syntax) &&
+		s.Assign.Equal(syntax.Assign) &&
+		s.Version.Equal(syntax.Version)
+}
+
+func (s *SyntaxExpr) Doc() []Expr {
+	return s.DocExpr
+}
+
+func (s *SyntaxExpr) Comment() Expr {
+	return s.CommentExpr
+}

+ 677 - 0
tools/goctl/api/parser/g4/ast/type.go

@@ -0,0 +1,677 @@
+package ast
+
+import (
+	"fmt"
+	"sort"
+
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+	"github.com/tal-tech/go-zero/tools/goctl/api/util"
+)
+
+type (
+	// TypeAlias、 TypeStruct
+	TypeExpr interface {
+		Doc() []Expr
+		Format() error
+		Equal(v interface{}) bool
+		NameExpr() Expr
+	}
+	TypeAlias struct {
+		Name        Expr
+		Assign      Expr
+		DataType    DataType
+		DocExpr     []Expr
+		CommentExpr Expr
+	}
+
+	TypeStruct struct {
+		Name    Expr
+		Struct  Expr
+		LBrace  Expr
+		RBrace  Expr
+		DocExpr []Expr
+		Fields  []*TypeField
+	}
+
+	TypeField struct {
+		IsAnonymous bool
+		// Name is nil if IsAnonymous
+		Name        Expr
+		DataType    DataType
+		Tag         Expr
+		DocExpr     []Expr
+		CommentExpr Expr
+	}
+
+	// Literal, Interface, Map, Array, Time, Pointer
+	DataType interface {
+		Expr() Expr
+		Equal(dt DataType) bool
+		Format() error
+		IsNotNil() bool
+	}
+
+	// int, bool, Foo,...
+	Literal struct {
+		Literal Expr
+	}
+
+	Interface struct {
+		Literal Expr
+	}
+
+	Map struct {
+		MapExpr Expr
+		Map     Expr
+		LBrack  Expr
+		RBrack  Expr
+		Key     Expr
+		Value   DataType
+	}
+
+	Array struct {
+		ArrayExpr Expr
+		LBrack    Expr
+		RBrack    Expr
+		Literal   DataType
+	}
+
+	Time struct {
+		Literal Expr
+	}
+
+	Pointer struct {
+		PointerExpr Expr
+		Star        Expr
+		Name        Expr
+	}
+)
+
+func (v *ApiVisitor) VisitTypeSpec(ctx *api.TypeSpecContext) interface{} {
+	if ctx.TypeLit() != nil {
+		return []TypeExpr{ctx.TypeLit().Accept(v).(TypeExpr)}
+	}
+	return ctx.TypeBlock().Accept(v)
+}
+
+func (v *ApiVisitor) VisitTypeLit(ctx *api.TypeLitContext) interface{} {
+	typeLit := ctx.TypeLitBody().Accept(v)
+	alias, ok := typeLit.(*TypeAlias)
+	if ok {
+		return alias
+	}
+
+	st, ok := typeLit.(*TypeStruct)
+	if ok {
+		return st
+	}
+
+	return typeLit
+}
+
+func (v *ApiVisitor) VisitTypeBlock(ctx *api.TypeBlockContext) interface{} {
+	list := ctx.AllTypeBlockBody()
+	var types []TypeExpr
+	for _, each := range list {
+		types = append(types, each.Accept(v).(TypeExpr))
+
+	}
+	return types
+}
+
+func (v *ApiVisitor) VisitTypeLitBody(ctx *api.TypeLitBodyContext) interface{} {
+	if ctx.TypeAlias() != nil {
+		return ctx.TypeAlias().Accept(v)
+	}
+	return ctx.TypeStruct().Accept(v)
+}
+
+func (v *ApiVisitor) VisitTypeBlockBody(ctx *api.TypeBlockBodyContext) interface{} {
+	if ctx.TypeBlockAlias() != nil {
+		return ctx.TypeBlockAlias().Accept(v).(*TypeAlias)
+	}
+	return ctx.TypeBlockStruct().Accept(v).(*TypeStruct)
+}
+
+func (v *ApiVisitor) VisitTypeStruct(ctx *api.TypeStructContext) interface{} {
+	var st TypeStruct
+	st.Name = v.newExprWithToken(ctx.GetStructName())
+	v.exportCheck(st.Name)
+
+	if util.UnExport(ctx.GetStructName().GetText()) {
+
+	}
+	if ctx.GetStructToken() != nil {
+		structExpr := v.newExprWithToken(ctx.GetStructToken())
+		structTokenText := ctx.GetStructToken().GetText()
+		if structTokenText != "struct" {
+			v.panic(structExpr, fmt.Sprintf("expecting 'struct', found input '%s'", structTokenText))
+		}
+
+		if api.IsGolangKeyWord(structTokenText, "struct") {
+			v.panic(structExpr, fmt.Sprintf("expecting 'struct', but found golang keyword '%s'", structTokenText))
+		}
+
+		st.Struct = structExpr
+	}
+
+	st.LBrace = v.newExprWithToken(ctx.GetLbrace())
+	st.RBrace = v.newExprWithToken(ctx.GetRbrace())
+	fields := ctx.AllField()
+	for _, each := range fields {
+		f := each.Accept(v)
+		if f == nil {
+			continue
+		}
+		st.Fields = append(st.Fields, f.(*TypeField))
+	}
+	return &st
+}
+
+func (v *ApiVisitor) VisitTypeBlockStruct(ctx *api.TypeBlockStructContext) interface{} {
+	var st TypeStruct
+	st.Name = v.newExprWithToken(ctx.GetStructName())
+	v.exportCheck(st.Name)
+
+	if ctx.GetStructToken() != nil {
+		structExpr := v.newExprWithToken(ctx.GetStructToken())
+		structTokenText := ctx.GetStructToken().GetText()
+		if structTokenText != "struct" {
+			v.panic(structExpr, fmt.Sprintf("expecting 'struct', found imput '%s'", structTokenText))
+		}
+
+		if api.IsGolangKeyWord(structTokenText, "struct") {
+			v.panic(structExpr, fmt.Sprintf("expecting 'struct', but found golang keyword '%s'", structTokenText))
+		}
+
+		st.Struct = structExpr
+	}
+	st.DocExpr = v.getDoc(ctx)
+	st.LBrace = v.newExprWithToken(ctx.GetLbrace())
+	st.RBrace = v.newExprWithToken(ctx.GetRbrace())
+	fields := ctx.AllField()
+	for _, each := range fields {
+		f := each.Accept(v)
+		if f == nil {
+			continue
+		}
+		st.Fields = append(st.Fields, f.(*TypeField))
+	}
+	return &st
+}
+
+func (v *ApiVisitor) VisitTypeBlockAlias(ctx *api.TypeBlockAliasContext) interface{} {
+	var alias TypeAlias
+	alias.Name = v.newExprWithToken(ctx.GetAlias())
+	alias.Assign = v.newExprWithToken(ctx.GetAssign())
+	alias.DataType = ctx.DataType().Accept(v).(DataType)
+	alias.DocExpr = v.getDoc(ctx)
+	alias.CommentExpr = v.getComment(ctx)
+	// todo: reopen if necessary
+	v.panic(alias.Name, "unsupport alias")
+	return &alias
+}
+
+func (v *ApiVisitor) VisitTypeAlias(ctx *api.TypeAliasContext) interface{} {
+	var alias TypeAlias
+	alias.Name = v.newExprWithToken(ctx.GetAlias())
+	alias.Assign = v.newExprWithToken(ctx.GetAssign())
+	alias.DataType = ctx.DataType().Accept(v).(DataType)
+	alias.DocExpr = v.getDoc(ctx)
+	alias.CommentExpr = v.getComment(ctx)
+	// todo: reopen if necessary
+	v.panic(alias.Name, "unsupport alias")
+	return &alias
+}
+
+func (v *ApiVisitor) VisitField(ctx *api.FieldContext) interface{} {
+	iAnonymousFiled := ctx.AnonymousFiled()
+	iNormalFieldContext := ctx.NormalField()
+	if iAnonymousFiled != nil {
+		return iAnonymousFiled.Accept(v).(*TypeField)
+	}
+	if iNormalFieldContext != nil {
+		return iNormalFieldContext.Accept(v).(*TypeField)
+	}
+	return nil
+}
+
+func (v *ApiVisitor) VisitNormalField(ctx *api.NormalFieldContext) interface{} {
+	var field TypeField
+	field.Name = v.newExprWithToken(ctx.GetFieldName())
+	v.exportCheck(field.Name)
+
+	iDataTypeContext := ctx.DataType()
+	if iDataTypeContext != nil {
+		field.DataType = iDataTypeContext.Accept(v).(DataType)
+		field.CommentExpr = v.getComment(ctx)
+	}
+	if ctx.GetTag() != nil {
+		tagText := ctx.GetTag().GetText()
+		tagExpr := v.newExprWithToken(ctx.GetTag())
+		if !api.MatchTag(tagText) {
+			v.panic(tagExpr, fmt.Sprintf("mismatched tag, found input '%s'", tagText))
+		}
+		field.Tag = tagExpr
+		field.CommentExpr = v.getComment(ctx)
+	}
+	field.DocExpr = v.getDoc(ctx)
+	return &field
+}
+
+func (v *ApiVisitor) VisitAnonymousFiled(ctx *api.AnonymousFiledContext) interface{} {
+	start := ctx.GetStart()
+	stop := ctx.GetStop()
+	var field TypeField
+	field.IsAnonymous = true
+	if ctx.GetStar() != nil {
+		nameExpr := v.newExprWithTerminalNode(ctx.ID())
+		v.exportCheck(nameExpr)
+		field.DataType = &Pointer{
+			PointerExpr: v.newExprWithText(ctx.GetStar().GetText()+ctx.ID().GetText(), start.GetLine(), start.GetColumn(), start.GetStart(), stop.GetStop()),
+			Star:        v.newExprWithToken(ctx.GetStar()),
+			Name:        nameExpr,
+		}
+	} else {
+		nameExpr := v.newExprWithTerminalNode(ctx.ID())
+		v.exportCheck(nameExpr)
+		field.DataType = &Literal{Literal: nameExpr}
+	}
+	field.DocExpr = v.getDoc(ctx)
+	field.CommentExpr = v.getComment(ctx)
+	return &field
+}
+
+func (v *ApiVisitor) VisitDataType(ctx *api.DataTypeContext) interface{} {
+	if ctx.ID() != nil {
+		idExpr := v.newExprWithTerminalNode(ctx.ID())
+		v.exportCheck(idExpr)
+		return &Literal{Literal: idExpr}
+	}
+	if ctx.MapType() != nil {
+		t := ctx.MapType().Accept(v)
+		return t
+	}
+	if ctx.ArrayType() != nil {
+		return ctx.ArrayType().Accept(v)
+	}
+	if ctx.GetInter() != nil {
+		return &Interface{Literal: v.newExprWithToken(ctx.GetInter())}
+	}
+	if ctx.GetTime() != nil {
+		// todo: reopen if it is necessary
+		timeExpr := v.newExprWithToken(ctx.GetTime())
+		v.panic(timeExpr, "unsupport time.Time")
+		return &Time{Literal: timeExpr}
+	}
+	if ctx.PointerType() != nil {
+		return ctx.PointerType().Accept(v)
+	}
+	return ctx.TypeStruct().Accept(v)
+}
+
+func (v *ApiVisitor) VisitPointerType(ctx *api.PointerTypeContext) interface{} {
+	nameExpr := v.newExprWithTerminalNode(ctx.ID())
+	v.exportCheck(nameExpr)
+	return &Pointer{
+		PointerExpr: v.newExprWithText(ctx.GetText(), ctx.GetStar().GetLine(), ctx.GetStar().GetColumn(), ctx.GetStar().GetStart(), ctx.ID().GetSymbol().GetStop()),
+		Star:        v.newExprWithToken(ctx.GetStar()),
+		Name:        nameExpr,
+	}
+}
+
+func (v *ApiVisitor) VisitMapType(ctx *api.MapTypeContext) interface{} {
+	return &Map{
+		MapExpr: v.newExprWithText(ctx.GetText(), ctx.GetMapToken().GetLine(), ctx.GetMapToken().GetColumn(),
+			ctx.GetMapToken().GetStart(), ctx.GetValue().GetStop().GetStop()),
+		Map:    v.newExprWithToken(ctx.GetMapToken()),
+		LBrack: v.newExprWithToken(ctx.GetLbrack()),
+		RBrack: v.newExprWithToken(ctx.GetRbrack()),
+		Key:    v.newExprWithToken(ctx.GetKey()),
+		Value:  ctx.GetValue().Accept(v).(DataType),
+	}
+}
+
+func (v *ApiVisitor) VisitArrayType(ctx *api.ArrayTypeContext) interface{} {
+	return &Array{
+		ArrayExpr: v.newExprWithText(ctx.GetText(), ctx.GetLbrack().GetLine(), ctx.GetLbrack().GetColumn(), ctx.GetLbrack().GetStart(), ctx.DataType().GetStop().GetStop()),
+		LBrack:    v.newExprWithToken(ctx.GetLbrack()),
+		RBrack:    v.newExprWithToken(ctx.GetRbrack()),
+		Literal:   ctx.DataType().Accept(v).(DataType),
+	}
+}
+
+func (a *TypeAlias) NameExpr() Expr {
+	return a.Name
+}
+
+func (a *TypeAlias) Doc() []Expr {
+	return a.DocExpr
+}
+
+func (a *TypeAlias) Comment() Expr {
+	return a.CommentExpr
+}
+
+func (a *TypeAlias) Format() error {
+	return nil
+}
+
+func (a *TypeAlias) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	alias := v.(*TypeAlias)
+	if !a.Name.Equal(alias.Name) {
+		return false
+	}
+
+	if !a.Assign.Equal(alias.Assign) {
+		return false
+	}
+
+	if !a.DataType.Equal(alias.DataType) {
+		return false
+	}
+
+	return EqualDoc(a, alias)
+}
+
+func (l *Literal) Expr() Expr {
+	return l.Literal
+}
+
+func (l *Literal) Format() error {
+	// todo
+	return nil
+}
+
+func (l *Literal) Equal(dt DataType) bool {
+	if dt == nil {
+		return false
+	}
+
+	v, ok := dt.(*Literal)
+	if !ok {
+		return false
+	}
+
+	return l.Literal.Equal(v.Literal)
+}
+
+func (l *Literal) IsNotNil() bool {
+	return l != nil
+}
+
+func (i *Interface) Expr() Expr {
+	return i.Literal
+}
+
+func (i *Interface) Format() error {
+	// todo
+	return nil
+}
+
+func (i *Interface) Equal(dt DataType) bool {
+	if dt == nil {
+		return false
+	}
+
+	v, ok := dt.(*Interface)
+	if !ok {
+		return false
+	}
+
+	return i.Literal.Equal(v.Literal)
+}
+
+func (i *Interface) IsNotNil() bool {
+	return i != nil
+}
+
+func (m *Map) Expr() Expr {
+	return m.MapExpr
+}
+
+func (m *Map) Format() error {
+	// todo
+	return nil
+}
+
+func (m *Map) Equal(dt DataType) bool {
+	if dt == nil {
+		return false
+	}
+
+	v, ok := dt.(*Map)
+	if !ok {
+		return false
+	}
+
+	if !m.Key.Equal(v.Key) {
+		return false
+	}
+
+	if !m.Value.Equal(v.Value) {
+		return false
+	}
+
+	if !m.MapExpr.Equal(v.MapExpr) {
+		return false
+	}
+
+	return m.Map.Equal(v.Map)
+}
+
+func (m *Map) IsNotNil() bool {
+	return m != nil
+}
+
+func (a *Array) Expr() Expr {
+	return a.ArrayExpr
+}
+
+func (a *Array) Format() error {
+	// todo
+	return nil
+}
+
+func (a *Array) Equal(dt DataType) bool {
+	if dt == nil {
+		return false
+	}
+
+	v, ok := dt.(*Array)
+	if !ok {
+		return false
+	}
+
+	if !a.ArrayExpr.Equal(v.ArrayExpr) {
+		return false
+	}
+
+	return a.Literal.Equal(v.Literal)
+}
+
+func (a *Array) IsNotNil() bool {
+	return a != nil
+}
+
+func (t *Time) Expr() Expr {
+	return t.Literal
+}
+
+func (t *Time) Format() error {
+	// todo
+	return nil
+}
+
+func (t *Time) Equal(dt DataType) bool {
+	if dt == nil {
+		return false
+	}
+
+	v, ok := dt.(*Time)
+	if !ok {
+		return false
+	}
+
+	return t.Literal.Equal(v.Literal)
+}
+
+func (t *Time) IsNotNil() bool {
+	return t != nil
+}
+
+func (p *Pointer) Expr() Expr {
+	return p.PointerExpr
+}
+
+func (p *Pointer) Format() error {
+	return nil
+}
+
+func (p *Pointer) Equal(dt DataType) bool {
+	if dt == nil {
+		return false
+	}
+
+	v, ok := dt.(*Pointer)
+	if !ok {
+		return false
+	}
+
+	if !p.PointerExpr.Equal(v.PointerExpr) {
+		return false
+	}
+
+	if !p.Star.Equal(v.Star) {
+		return false
+	}
+
+	return p.Name.Equal(v.Name)
+}
+
+func (p *Pointer) IsNotNil() bool {
+	return p != nil
+}
+
+func (s *TypeStruct) NameExpr() Expr {
+	return s.Name
+}
+
+func (s *TypeStruct) Equal(dt interface{}) bool {
+	if dt == nil {
+		return false
+	}
+
+	v, ok := dt.(*TypeStruct)
+	if !ok {
+		return false
+	}
+
+	if !s.Name.Equal(v.Name) {
+		return false
+	}
+
+	var expectDoc, actualDoc []Expr
+	expectDoc = append(expectDoc, s.DocExpr...)
+	actualDoc = append(actualDoc, v.DocExpr...)
+	sort.Slice(expectDoc, func(i, j int) bool {
+		return expectDoc[i].Line() < expectDoc[j].Line()
+	})
+
+	for index, each := range actualDoc {
+		if !each.Equal(actualDoc[index]) {
+			return false
+		}
+	}
+
+	if s.Struct != nil {
+		if s.Struct != nil {
+			if !s.Struct.Equal(v.Struct) {
+				return false
+			}
+		}
+	}
+
+	if len(s.Fields) != len(v.Fields) {
+		return false
+	}
+
+	var expected, acual []*TypeField
+	expected = append(expected, s.Fields...)
+	acual = append(acual, v.Fields...)
+
+	sort.Slice(expected, func(i, j int) bool {
+		return expected[i].DataType.Expr().Line() < expected[j].DataType.Expr().Line()
+	})
+	sort.Slice(acual, func(i, j int) bool {
+		return acual[i].DataType.Expr().Line() < acual[j].DataType.Expr().Line()
+	})
+
+	for index, each := range expected {
+		ac := acual[index]
+		if !each.Equal(ac) {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (s *TypeStruct) Doc() []Expr {
+	return s.DocExpr
+}
+
+func (s *TypeStruct) Format() error {
+	// todo
+	return nil
+}
+
+func (t *TypeField) Equal(v interface{}) bool {
+	if v == nil {
+		return false
+	}
+
+	f, ok := v.(*TypeField)
+	if !ok {
+		return false
+	}
+
+	if t.IsAnonymous != f.IsAnonymous {
+		return false
+	}
+
+	if !t.DataType.Equal(f.DataType) {
+		return false
+	}
+
+	if !t.IsAnonymous {
+		if !t.Name.Equal(f.Name) {
+			return false
+		}
+
+		if t.Tag != nil {
+			if !t.Tag.Equal(f.Tag) {
+				return false
+			}
+		}
+	}
+
+	return EqualDoc(t, f)
+}
+
+func (t *TypeField) Doc() []Expr {
+	return t.DocExpr
+}
+
+func (t *TypeField) Comment() Expr {
+	return t.CommentExpr
+}
+
+func (t *TypeField) Format() error {
+	// todo
+	return nil
+}

+ 156 - 0
tools/goctl/api/parser/g4/gen/api/apiparser_base_visitor.go

@@ -0,0 +1,156 @@
+// Code generated from tools/goctl/api/parser/g4/ApiParser.g4 by ANTLR 4.9. DO NOT EDIT.
+
+package api // ApiParser
+import "github.com/antlr/antlr4/runtime/Go/antlr"
+
+type BaseApiParserVisitor struct {
+	*antlr.BaseParseTreeVisitor
+}
+
+func (v *BaseApiParserVisitor) VisitApi(ctx *ApiContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitSpec(ctx *SpecContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitSyntaxLit(ctx *SyntaxLitContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitImportSpec(ctx *ImportSpecContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitImportLit(ctx *ImportLitContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitImportBlock(ctx *ImportBlockContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitImportBlockValue(ctx *ImportBlockValueContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitImportValue(ctx *ImportValueContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitInfoSpec(ctx *InfoSpecContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitTypeSpec(ctx *TypeSpecContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitTypeLit(ctx *TypeLitContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitTypeBlock(ctx *TypeBlockContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitTypeLitBody(ctx *TypeLitBodyContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitTypeBlockBody(ctx *TypeBlockBodyContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitTypeStruct(ctx *TypeStructContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitTypeAlias(ctx *TypeAliasContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitTypeBlockStruct(ctx *TypeBlockStructContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitTypeBlockAlias(ctx *TypeBlockAliasContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitField(ctx *FieldContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitNormalField(ctx *NormalFieldContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitAnonymousFiled(ctx *AnonymousFiledContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitDataType(ctx *DataTypeContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitPointerType(ctx *PointerTypeContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitMapType(ctx *MapTypeContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitArrayType(ctx *ArrayTypeContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitServiceSpec(ctx *ServiceSpecContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitAtServer(ctx *AtServerContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitServiceApi(ctx *ServiceApiContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitServiceRoute(ctx *ServiceRouteContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitAtDoc(ctx *AtDocContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitAtHandler(ctx *AtHandlerContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitRoute(ctx *RouteContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitBody(ctx *BodyContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitReplybody(ctx *ReplybodyContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitKvLit(ctx *KvLitContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitServiceName(ctx *ServiceNameContext) interface{} {
+	return v.VisitChildren(ctx)
+}
+
+func (v *BaseApiParserVisitor) VisitPath(ctx *PathContext) interface{} {
+	return v.VisitChildren(ctx)
+}

+ 234 - 0
tools/goctl/api/parser/g4/gen/api/apiparser_lexer.go

@@ -0,0 +1,234 @@
+// Code generated from tools/goctl/api/parser/g4/ApiParser.g4 by ANTLR 4.9. DO NOT EDIT.
+
+package api
+
+import (
+	"fmt"
+	"unicode"
+
+	"github.com/antlr/antlr4/runtime/Go/antlr"
+)
+
+// Suppress unused import error
+var _ = fmt.Printf
+var _ = unicode.IsLetter
+
+var serializedLexerAtn = []uint16{
+	3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 25, 266,
+	8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7,
+	9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12,
+	4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4,
+	18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23,
+	9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9,
+	28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 3, 2, 3, 2, 3, 3, 3, 3, 3,
+	4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 3, 8, 3,
+	8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11,
+	3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3,
+	15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16,
+	3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3, 16, 3,
+	17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 17, 3, 18, 6, 18, 132,
+	10, 18, 13, 18, 14, 18, 133, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 19,
+	7, 19, 142, 10, 19, 12, 19, 14, 19, 145, 11, 19, 3, 19, 3, 19, 3, 19, 3,
+	19, 3, 19, 3, 20, 3, 20, 3, 20, 3, 20, 7, 20, 156, 10, 20, 12, 20, 14,
+	20, 159, 11, 20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 7, 21, 166, 10, 21,
+	12, 21, 14, 21, 169, 11, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 22, 6, 22,
+	176, 10, 22, 13, 22, 14, 22, 177, 3, 22, 3, 22, 3, 23, 3, 23, 7, 23, 184,
+	10, 23, 12, 23, 14, 23, 187, 11, 23, 3, 23, 3, 23, 7, 23, 191, 10, 23,
+	12, 23, 14, 23, 194, 11, 23, 5, 23, 196, 10, 23, 3, 24, 3, 24, 7, 24, 200,
+	10, 24, 12, 24, 14, 24, 203, 11, 24, 3, 25, 3, 25, 5, 25, 207, 10, 25,
+	3, 25, 3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 5, 26, 215, 10, 26, 3, 26, 5,
+	26, 218, 10, 26, 3, 26, 3, 26, 3, 26, 6, 26, 223, 10, 26, 13, 26, 14, 26,
+	224, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 5, 26, 232, 10, 26, 3, 27, 3, 27,
+	3, 27, 7, 27, 237, 10, 27, 12, 27, 14, 27, 240, 11, 27, 3, 27, 5, 27, 243,
+	10, 27, 3, 28, 3, 28, 3, 29, 3, 29, 7, 29, 249, 10, 29, 12, 29, 14, 29,
+	252, 11, 29, 3, 29, 5, 29, 255, 10, 29, 3, 30, 3, 30, 5, 30, 259, 10, 30,
+	3, 31, 3, 31, 3, 31, 3, 31, 5, 31, 265, 10, 31, 3, 143, 2, 32, 3, 3, 5,
+	4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 13, 25,
+	14, 27, 15, 29, 16, 31, 17, 33, 18, 35, 19, 37, 20, 39, 21, 41, 22, 43,
+	23, 45, 24, 47, 25, 49, 2, 51, 2, 53, 2, 55, 2, 57, 2, 59, 2, 61, 2, 3,
+	2, 20, 5, 2, 11, 12, 14, 15, 34, 34, 4, 2, 12, 12, 15, 15, 4, 2, 36, 36,
+	94, 94, 6, 2, 12, 12, 15, 15, 94, 94, 98, 98, 4, 2, 11, 11, 34, 34, 6,
+	2, 12, 12, 15, 15, 36, 36, 98, 98, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45,
+	47, 47, 10, 2, 36, 36, 41, 41, 94, 94, 100, 100, 104, 104, 112, 112, 116,
+	116, 118, 118, 3, 2, 50, 53, 3, 2, 50, 57, 5, 2, 50, 59, 67, 72, 99, 104,
+	3, 2, 50, 59, 4, 2, 50, 59, 97, 97, 6, 2, 38, 38, 67, 92, 97, 97, 99, 124,
+	4, 2, 2, 129, 55298, 56321, 3, 2, 55298, 56321, 3, 2, 56322, 57345, 2,
+	283, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2,
+	2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3,
+	2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25,
+	3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2,
+	33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3, 2, 2, 2,
+	2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47, 3, 2, 2,
+	2, 3, 63, 3, 2, 2, 2, 5, 65, 3, 2, 2, 2, 7, 67, 3, 2, 2, 2, 9, 69, 3, 2,
+	2, 2, 11, 71, 3, 2, 2, 2, 13, 73, 3, 2, 2, 2, 15, 75, 3, 2, 2, 2, 17, 85,
+	3, 2, 2, 2, 19, 87, 3, 2, 2, 2, 21, 89, 3, 2, 2, 2, 23, 91, 3, 2, 2, 2,
+	25, 93, 3, 2, 2, 2, 27, 96, 3, 2, 2, 2, 29, 101, 3, 2, 2, 2, 31, 110, 3,
+	2, 2, 2, 33, 122, 3, 2, 2, 2, 35, 131, 3, 2, 2, 2, 37, 137, 3, 2, 2, 2,
+	39, 151, 3, 2, 2, 2, 41, 162, 3, 2, 2, 2, 43, 172, 3, 2, 2, 2, 45, 181,
+	3, 2, 2, 2, 47, 197, 3, 2, 2, 2, 49, 204, 3, 2, 2, 2, 51, 231, 3, 2, 2,
+	2, 53, 233, 3, 2, 2, 2, 55, 244, 3, 2, 2, 2, 57, 246, 3, 2, 2, 2, 59, 258,
+	3, 2, 2, 2, 61, 264, 3, 2, 2, 2, 63, 64, 7, 63, 2, 2, 64, 4, 3, 2, 2, 2,
+	65, 66, 7, 42, 2, 2, 66, 6, 3, 2, 2, 2, 67, 68, 7, 43, 2, 2, 68, 8, 3,
+	2, 2, 2, 69, 70, 7, 125, 2, 2, 70, 10, 3, 2, 2, 2, 71, 72, 7, 127, 2, 2,
+	72, 12, 3, 2, 2, 2, 73, 74, 7, 44, 2, 2, 74, 14, 3, 2, 2, 2, 75, 76, 7,
+	118, 2, 2, 76, 77, 7, 107, 2, 2, 77, 78, 7, 111, 2, 2, 78, 79, 7, 103,
+	2, 2, 79, 80, 7, 48, 2, 2, 80, 81, 7, 86, 2, 2, 81, 82, 7, 107, 2, 2, 82,
+	83, 7, 111, 2, 2, 83, 84, 7, 103, 2, 2, 84, 16, 3, 2, 2, 2, 85, 86, 7,
+	93, 2, 2, 86, 18, 3, 2, 2, 2, 87, 88, 7, 95, 2, 2, 88, 20, 3, 2, 2, 2,
+	89, 90, 7, 47, 2, 2, 90, 22, 3, 2, 2, 2, 91, 92, 7, 49, 2, 2, 92, 24, 3,
+	2, 2, 2, 93, 94, 7, 49, 2, 2, 94, 95, 7, 60, 2, 2, 95, 26, 3, 2, 2, 2,
+	96, 97, 7, 66, 2, 2, 97, 98, 7, 102, 2, 2, 98, 99, 7, 113, 2, 2, 99, 100,
+	7, 101, 2, 2, 100, 28, 3, 2, 2, 2, 101, 102, 7, 66, 2, 2, 102, 103, 7,
+	106, 2, 2, 103, 104, 7, 99, 2, 2, 104, 105, 7, 112, 2, 2, 105, 106, 7,
+	102, 2, 2, 106, 107, 7, 110, 2, 2, 107, 108, 7, 103, 2, 2, 108, 109, 7,
+	116, 2, 2, 109, 30, 3, 2, 2, 2, 110, 111, 7, 107, 2, 2, 111, 112, 7, 112,
+	2, 2, 112, 113, 7, 118, 2, 2, 113, 114, 7, 103, 2, 2, 114, 115, 7, 116,
+	2, 2, 115, 116, 7, 104, 2, 2, 116, 117, 7, 99, 2, 2, 117, 118, 7, 101,
+	2, 2, 118, 119, 7, 103, 2, 2, 119, 120, 7, 125, 2, 2, 120, 121, 7, 127,
+	2, 2, 121, 32, 3, 2, 2, 2, 122, 123, 7, 66, 2, 2, 123, 124, 7, 117, 2,
+	2, 124, 125, 7, 103, 2, 2, 125, 126, 7, 116, 2, 2, 126, 127, 7, 120, 2,
+	2, 127, 128, 7, 103, 2, 2, 128, 129, 7, 116, 2, 2, 129, 34, 3, 2, 2, 2,
+	130, 132, 9, 2, 2, 2, 131, 130, 3, 2, 2, 2, 132, 133, 3, 2, 2, 2, 133,
+	131, 3, 2, 2, 2, 133, 134, 3, 2, 2, 2, 134, 135, 3, 2, 2, 2, 135, 136,
+	8, 18, 2, 2, 136, 36, 3, 2, 2, 2, 137, 138, 7, 49, 2, 2, 138, 139, 7, 44,
+	2, 2, 139, 143, 3, 2, 2, 2, 140, 142, 11, 2, 2, 2, 141, 140, 3, 2, 2, 2,
+	142, 145, 3, 2, 2, 2, 143, 144, 3, 2, 2, 2, 143, 141, 3, 2, 2, 2, 144,
+	146, 3, 2, 2, 2, 145, 143, 3, 2, 2, 2, 146, 147, 7, 44, 2, 2, 147, 148,
+	7, 49, 2, 2, 148, 149, 3, 2, 2, 2, 149, 150, 8, 19, 3, 2, 150, 38, 3, 2,
+	2, 2, 151, 152, 7, 49, 2, 2, 152, 153, 7, 49, 2, 2, 153, 157, 3, 2, 2,
+	2, 154, 156, 10, 3, 2, 2, 155, 154, 3, 2, 2, 2, 156, 159, 3, 2, 2, 2, 157,
+	155, 3, 2, 2, 2, 157, 158, 3, 2, 2, 2, 158, 160, 3, 2, 2, 2, 159, 157,
+	3, 2, 2, 2, 160, 161, 8, 20, 3, 2, 161, 40, 3, 2, 2, 2, 162, 167, 7, 36,
+	2, 2, 163, 166, 10, 4, 2, 2, 164, 166, 5, 51, 26, 2, 165, 163, 3, 2, 2,
+	2, 165, 164, 3, 2, 2, 2, 166, 169, 3, 2, 2, 2, 167, 165, 3, 2, 2, 2, 167,
+	168, 3, 2, 2, 2, 168, 170, 3, 2, 2, 2, 169, 167, 3, 2, 2, 2, 170, 171,
+	7, 36, 2, 2, 171, 42, 3, 2, 2, 2, 172, 175, 7, 98, 2, 2, 173, 176, 10,
+	5, 2, 2, 174, 176, 5, 51, 26, 2, 175, 173, 3, 2, 2, 2, 175, 174, 3, 2,
+	2, 2, 176, 177, 3, 2, 2, 2, 177, 175, 3, 2, 2, 2, 177, 178, 3, 2, 2, 2,
+	178, 179, 3, 2, 2, 2, 179, 180, 7, 98, 2, 2, 180, 44, 3, 2, 2, 2, 181,
+	185, 7, 60, 2, 2, 182, 184, 9, 6, 2, 2, 183, 182, 3, 2, 2, 2, 184, 187,
+	3, 2, 2, 2, 185, 183, 3, 2, 2, 2, 185, 186, 3, 2, 2, 2, 186, 195, 3, 2,
+	2, 2, 187, 185, 3, 2, 2, 2, 188, 196, 5, 41, 21, 2, 189, 191, 10, 7, 2,
+	2, 190, 189, 3, 2, 2, 2, 191, 194, 3, 2, 2, 2, 192, 190, 3, 2, 2, 2, 192,
+	193, 3, 2, 2, 2, 193, 196, 3, 2, 2, 2, 194, 192, 3, 2, 2, 2, 195, 188,
+	3, 2, 2, 2, 195, 192, 3, 2, 2, 2, 196, 46, 3, 2, 2, 2, 197, 201, 5, 61,
+	31, 2, 198, 200, 5, 59, 30, 2, 199, 198, 3, 2, 2, 2, 200, 203, 3, 2, 2,
+	2, 201, 199, 3, 2, 2, 2, 201, 202, 3, 2, 2, 2, 202, 48, 3, 2, 2, 2, 203,
+	201, 3, 2, 2, 2, 204, 206, 9, 8, 2, 2, 205, 207, 9, 9, 2, 2, 206, 205,
+	3, 2, 2, 2, 206, 207, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 209, 5, 57,
+	29, 2, 209, 50, 3, 2, 2, 2, 210, 211, 7, 94, 2, 2, 211, 232, 9, 10, 2,
+	2, 212, 217, 7, 94, 2, 2, 213, 215, 9, 11, 2, 2, 214, 213, 3, 2, 2, 2,
+	214, 215, 3, 2, 2, 2, 215, 216, 3, 2, 2, 2, 216, 218, 9, 12, 2, 2, 217,
+	214, 3, 2, 2, 2, 217, 218, 3, 2, 2, 2, 218, 219, 3, 2, 2, 2, 219, 232,
+	9, 12, 2, 2, 220, 222, 7, 94, 2, 2, 221, 223, 7, 119, 2, 2, 222, 221, 3,
+	2, 2, 2, 223, 224, 3, 2, 2, 2, 224, 222, 3, 2, 2, 2, 224, 225, 3, 2, 2,
+	2, 225, 226, 3, 2, 2, 2, 226, 227, 5, 55, 28, 2, 227, 228, 5, 55, 28, 2,
+	228, 229, 5, 55, 28, 2, 229, 230, 5, 55, 28, 2, 230, 232, 3, 2, 2, 2, 231,
+	210, 3, 2, 2, 2, 231, 212, 3, 2, 2, 2, 231, 220, 3, 2, 2, 2, 232, 52, 3,
+	2, 2, 2, 233, 242, 5, 55, 28, 2, 234, 237, 5, 55, 28, 2, 235, 237, 7, 97,
+	2, 2, 236, 234, 3, 2, 2, 2, 236, 235, 3, 2, 2, 2, 237, 240, 3, 2, 2, 2,
+	238, 236, 3, 2, 2, 2, 238, 239, 3, 2, 2, 2, 239, 241, 3, 2, 2, 2, 240,
+	238, 3, 2, 2, 2, 241, 243, 5, 55, 28, 2, 242, 238, 3, 2, 2, 2, 242, 243,
+	3, 2, 2, 2, 243, 54, 3, 2, 2, 2, 244, 245, 9, 13, 2, 2, 245, 56, 3, 2,
+	2, 2, 246, 254, 9, 14, 2, 2, 247, 249, 9, 15, 2, 2, 248, 247, 3, 2, 2,
+	2, 249, 252, 3, 2, 2, 2, 250, 248, 3, 2, 2, 2, 250, 251, 3, 2, 2, 2, 251,
+	253, 3, 2, 2, 2, 252, 250, 3, 2, 2, 2, 253, 255, 9, 14, 2, 2, 254, 250,
+	3, 2, 2, 2, 254, 255, 3, 2, 2, 2, 255, 58, 3, 2, 2, 2, 256, 259, 5, 61,
+	31, 2, 257, 259, 9, 14, 2, 2, 258, 256, 3, 2, 2, 2, 258, 257, 3, 2, 2,
+	2, 259, 60, 3, 2, 2, 2, 260, 265, 9, 16, 2, 2, 261, 265, 10, 17, 2, 2,
+	262, 263, 9, 18, 2, 2, 263, 265, 9, 19, 2, 2, 264, 260, 3, 2, 2, 2, 264,
+	261, 3, 2, 2, 2, 264, 262, 3, 2, 2, 2, 265, 62, 3, 2, 2, 2, 26, 2, 133,
+	143, 157, 165, 167, 175, 177, 185, 192, 195, 201, 206, 214, 217, 224, 231,
+	236, 238, 242, 250, 254, 258, 264, 4, 2, 3, 2, 2, 90, 2,
+}
+
+var lexerChannelNames = []string{
+	"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
+}
+
+var lexerModeNames = []string{
+	"DEFAULT_MODE",
+}
+
+var lexerLiteralNames = []string{
+	"", "'='", "'('", "')'", "'{'", "'}'", "'*'", "'time.Time'", "'['", "']'",
+	"'-'", "'/'", "'/:'", "'@doc'", "'@handler'", "'interface{}'", "'@server'",
+}
+
+var lexerSymbolicNames = []string{
+	"", "", "", "", "", "", "", "", "", "", "", "", "", "ATDOC", "ATHANDLER",
+	"INTERFACE", "ATSERVER", "WS", "COMMENT", "LINE_COMMENT", "STRING", "RAW_STRING",
+	"LINE_VALUE", "ID",
+}
+
+var lexerRuleNames = []string{
+	"T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8",
+	"T__9", "T__10", "T__11", "ATDOC", "ATHANDLER", "INTERFACE", "ATSERVER",
+	"WS", "COMMENT", "LINE_COMMENT", "STRING", "RAW_STRING", "LINE_VALUE",
+	"ID", "ExponentPart", "EscapeSequence", "HexDigits", "HexDigit", "Digits",
+	"LetterOrDigit", "Letter",
+}
+
+type ApiParserLexer struct {
+	*antlr.BaseLexer
+	channelNames []string
+	modeNames    []string
+	// TODO: EOF string
+}
+
+// NewApiParserLexer produces a new lexer instance for the optional input antlr.CharStream.
+//
+// The *ApiParserLexer instance produced may be reused by calling the SetInputStream method.
+// The initial lexer configuration is expensive to construct, and the object is not thread-safe;
+// however, if used within a Golang sync.Pool, the construction cost amortizes well and the
+// objects can be used in a thread-safe manner.
+func NewApiParserLexer(input antlr.CharStream) *ApiParserLexer {
+	l := new(ApiParserLexer)
+	lexerDeserializer := antlr.NewATNDeserializer(nil)
+	lexerAtn := lexerDeserializer.DeserializeFromUInt16(serializedLexerAtn)
+	lexerDecisionToDFA := make([]*antlr.DFA, len(lexerAtn.DecisionToState))
+	for index, ds := range lexerAtn.DecisionToState {
+		lexerDecisionToDFA[index] = antlr.NewDFA(ds, index)
+	}
+	l.BaseLexer = antlr.NewBaseLexer(input)
+	l.Interpreter = antlr.NewLexerATNSimulator(l, lexerAtn, lexerDecisionToDFA, antlr.NewPredictionContextCache())
+
+	l.channelNames = lexerChannelNames
+	l.modeNames = lexerModeNames
+	l.RuleNames = lexerRuleNames
+	l.LiteralNames = lexerLiteralNames
+	l.SymbolicNames = lexerSymbolicNames
+	l.GrammarFileName = "ApiParser.g4"
+	// TODO: l.EOF = antlr.TokenEOF
+
+	return l
+}
+
+// ApiParserLexer tokens.
+const (
+	ApiParserLexerT__0         = 1
+	ApiParserLexerT__1         = 2
+	ApiParserLexerT__2         = 3
+	ApiParserLexerT__3         = 4
+	ApiParserLexerT__4         = 5
+	ApiParserLexerT__5         = 6
+	ApiParserLexerT__6         = 7
+	ApiParserLexerT__7         = 8
+	ApiParserLexerT__8         = 9
+	ApiParserLexerT__9         = 10
+	ApiParserLexerT__10        = 11
+	ApiParserLexerT__11        = 12
+	ApiParserLexerATDOC        = 13
+	ApiParserLexerATHANDLER    = 14
+	ApiParserLexerINTERFACE    = 15
+	ApiParserLexerATSERVER     = 16
+	ApiParserLexerWS           = 17
+	ApiParserLexerCOMMENT      = 18
+	ApiParserLexerLINE_COMMENT = 19
+	ApiParserLexerSTRING       = 20
+	ApiParserLexerRAW_STRING   = 21
+	ApiParserLexerLINE_VALUE   = 22
+	ApiParserLexerID           = 23
+)
+
+const COMEMNTS = 88

+ 5688 - 0
tools/goctl/api/parser/g4/gen/api/apiparser_parser.go

@@ -0,0 +1,5688 @@
+// Code generated from tools/goctl/api/parser/g4/ApiParser.g4 by ANTLR 4.9. DO NOT EDIT.
+
+package api // ApiParser
+import (
+	"fmt"
+	"reflect"
+	"strconv"
+
+	"github.com/antlr/antlr4/runtime/Go/antlr"
+)
+
+// Suppress unused import errors
+var _ = fmt.Printf
+var _ = reflect.Copy
+var _ = strconv.Itoa
+
+var parserATN = []uint16{
+	3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 25, 348,
+	4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7,
+	4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13,
+	9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9,
+	18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23,
+	4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4,
+	29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34,
+	9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 3, 2, 7,
+	2, 78, 10, 2, 12, 2, 14, 2, 81, 11, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5,
+	3, 88, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 5, 5, 98,
+	10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 7, 6, 7, 108, 10, 7,
+	13, 7, 14, 7, 109, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3,
+	10, 3, 10, 3, 10, 6, 10, 123, 10, 10, 13, 10, 14, 10, 124, 3, 10, 3, 10,
+	3, 11, 3, 11, 5, 11, 131, 10, 11, 3, 12, 3, 12, 3, 12, 3, 12, 3, 13, 3,
+	13, 3, 13, 3, 13, 7, 13, 141, 10, 13, 12, 13, 14, 13, 144, 11, 13, 3, 13,
+	3, 13, 3, 14, 3, 14, 5, 14, 150, 10, 14, 3, 15, 3, 15, 5, 15, 154, 10,
+	15, 3, 16, 3, 16, 3, 16, 5, 16, 159, 10, 16, 3, 16, 3, 16, 7, 16, 163,
+	10, 16, 12, 16, 14, 16, 166, 11, 16, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17,
+	5, 17, 173, 10, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 5, 18, 180, 10,
+	18, 3, 18, 3, 18, 7, 18, 184, 10, 18, 12, 18, 14, 18, 187, 11, 18, 3, 18,
+	3, 18, 3, 19, 3, 19, 3, 19, 5, 19, 194, 10, 19, 3, 19, 3, 19, 3, 20, 3,
+	20, 3, 20, 5, 20, 201, 10, 20, 3, 21, 3, 21, 3, 21, 3, 21, 5, 21, 207,
+	10, 21, 3, 22, 5, 22, 210, 10, 22, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3,
+	23, 3, 23, 3, 23, 3, 23, 3, 23, 5, 23, 222, 10, 23, 3, 24, 3, 24, 3, 24,
+	3, 24, 3, 25, 3, 25, 3, 25, 3, 25, 3, 25, 3, 25, 3, 25, 3, 25, 3, 26, 3,
+	26, 3, 26, 3, 26, 3, 27, 5, 27, 241, 10, 27, 3, 27, 3, 27, 3, 28, 3, 28,
+	3, 28, 6, 28, 248, 10, 28, 13, 28, 14, 28, 249, 3, 28, 3, 28, 3, 29, 3,
+	29, 3, 29, 3, 29, 3, 29, 7, 29, 259, 10, 29, 12, 29, 14, 29, 262, 11, 29,
+	3, 29, 3, 29, 3, 30, 5, 30, 267, 10, 30, 3, 30, 3, 30, 5, 30, 271, 10,
+	30, 3, 30, 3, 30, 3, 31, 3, 31, 5, 31, 277, 10, 31, 3, 31, 6, 31, 280,
+	10, 31, 13, 31, 14, 31, 281, 3, 31, 5, 31, 285, 10, 31, 3, 31, 5, 31, 288,
+	10, 31, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 33, 5, 33, 297, 10,
+	33, 3, 33, 5, 33, 300, 10, 33, 3, 33, 5, 33, 303, 10, 33, 3, 34, 3, 34,
+	5, 34, 307, 10, 34, 3, 34, 3, 34, 3, 35, 3, 35, 5, 35, 313, 10, 35, 3,
+	35, 3, 35, 3, 36, 3, 36, 3, 36, 3, 36, 3, 37, 3, 37, 5, 37, 323, 10, 37,
+	6, 37, 325, 10, 37, 13, 37, 14, 37, 326, 3, 38, 3, 38, 3, 38, 3, 38, 7,
+	38, 333, 10, 38, 12, 38, 14, 38, 336, 11, 38, 3, 38, 3, 38, 3, 38, 3, 38,
+	5, 38, 342, 10, 38, 6, 38, 344, 10, 38, 13, 38, 14, 38, 345, 3, 38, 2,
+	2, 39, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34,
+	36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70,
+	72, 74, 2, 2, 2, 357, 2, 79, 3, 2, 2, 2, 4, 87, 3, 2, 2, 2, 6, 89, 3, 2,
+	2, 2, 8, 97, 3, 2, 2, 2, 10, 99, 3, 2, 2, 2, 12, 103, 3, 2, 2, 2, 14, 113,
+	3, 2, 2, 2, 16, 115, 3, 2, 2, 2, 18, 118, 3, 2, 2, 2, 20, 130, 3, 2, 2,
+	2, 22, 132, 3, 2, 2, 2, 24, 136, 3, 2, 2, 2, 26, 149, 3, 2, 2, 2, 28, 153,
+	3, 2, 2, 2, 30, 155, 3, 2, 2, 2, 32, 169, 3, 2, 2, 2, 34, 176, 3, 2, 2,
+	2, 36, 190, 3, 2, 2, 2, 38, 200, 3, 2, 2, 2, 40, 202, 3, 2, 2, 2, 42, 209,
+	3, 2, 2, 2, 44, 221, 3, 2, 2, 2, 46, 223, 3, 2, 2, 2, 48, 227, 3, 2, 2,
+	2, 50, 235, 3, 2, 2, 2, 52, 240, 3, 2, 2, 2, 54, 244, 3, 2, 2, 2, 56, 253,
+	3, 2, 2, 2, 58, 266, 3, 2, 2, 2, 60, 274, 3, 2, 2, 2, 62, 289, 3, 2, 2,
+	2, 64, 292, 3, 2, 2, 2, 66, 304, 3, 2, 2, 2, 68, 310, 3, 2, 2, 2, 70, 316,
+	3, 2, 2, 2, 72, 324, 3, 2, 2, 2, 74, 343, 3, 2, 2, 2, 76, 78, 5, 4, 3,
+	2, 77, 76, 3, 2, 2, 2, 78, 81, 3, 2, 2, 2, 79, 77, 3, 2, 2, 2, 79, 80,
+	3, 2, 2, 2, 80, 3, 3, 2, 2, 2, 81, 79, 3, 2, 2, 2, 82, 88, 5, 6, 4, 2,
+	83, 88, 5, 8, 5, 2, 84, 88, 5, 18, 10, 2, 85, 88, 5, 20, 11, 2, 86, 88,
+	5, 52, 27, 2, 87, 82, 3, 2, 2, 2, 87, 83, 3, 2, 2, 2, 87, 84, 3, 2, 2,
+	2, 87, 85, 3, 2, 2, 2, 87, 86, 3, 2, 2, 2, 88, 5, 3, 2, 2, 2, 89, 90, 8,
+	4, 1, 2, 90, 91, 7, 25, 2, 2, 91, 92, 7, 3, 2, 2, 92, 93, 8, 4, 1, 2, 93,
+	94, 7, 22, 2, 2, 94, 7, 3, 2, 2, 2, 95, 98, 5, 10, 6, 2, 96, 98, 5, 12,
+	7, 2, 97, 95, 3, 2, 2, 2, 97, 96, 3, 2, 2, 2, 98, 9, 3, 2, 2, 2, 99, 100,
+	8, 6, 1, 2, 100, 101, 7, 25, 2, 2, 101, 102, 5, 16, 9, 2, 102, 11, 3, 2,
+	2, 2, 103, 104, 8, 7, 1, 2, 104, 105, 7, 25, 2, 2, 105, 107, 7, 4, 2, 2,
+	106, 108, 5, 14, 8, 2, 107, 106, 3, 2, 2, 2, 108, 109, 3, 2, 2, 2, 109,
+	107, 3, 2, 2, 2, 109, 110, 3, 2, 2, 2, 110, 111, 3, 2, 2, 2, 111, 112,
+	7, 5, 2, 2, 112, 13, 3, 2, 2, 2, 113, 114, 5, 16, 9, 2, 114, 15, 3, 2,
+	2, 2, 115, 116, 8, 9, 1, 2, 116, 117, 7, 22, 2, 2, 117, 17, 3, 2, 2, 2,
+	118, 119, 8, 10, 1, 2, 119, 120, 7, 25, 2, 2, 120, 122, 7, 4, 2, 2, 121,
+	123, 5, 70, 36, 2, 122, 121, 3, 2, 2, 2, 123, 124, 3, 2, 2, 2, 124, 122,
+	3, 2, 2, 2, 124, 125, 3, 2, 2, 2, 125, 126, 3, 2, 2, 2, 126, 127, 7, 5,
+	2, 2, 127, 19, 3, 2, 2, 2, 128, 131, 5, 22, 12, 2, 129, 131, 5, 24, 13,
+	2, 130, 128, 3, 2, 2, 2, 130, 129, 3, 2, 2, 2, 131, 21, 3, 2, 2, 2, 132,
+	133, 8, 12, 1, 2, 133, 134, 7, 25, 2, 2, 134, 135, 5, 26, 14, 2, 135, 23,
+	3, 2, 2, 2, 136, 137, 8, 13, 1, 2, 137, 138, 7, 25, 2, 2, 138, 142, 7,
+	4, 2, 2, 139, 141, 5, 28, 15, 2, 140, 139, 3, 2, 2, 2, 141, 144, 3, 2,
+	2, 2, 142, 140, 3, 2, 2, 2, 142, 143, 3, 2, 2, 2, 143, 145, 3, 2, 2, 2,
+	144, 142, 3, 2, 2, 2, 145, 146, 7, 5, 2, 2, 146, 25, 3, 2, 2, 2, 147, 150,
+	5, 30, 16, 2, 148, 150, 5, 32, 17, 2, 149, 147, 3, 2, 2, 2, 149, 148, 3,
+	2, 2, 2, 150, 27, 3, 2, 2, 2, 151, 154, 5, 34, 18, 2, 152, 154, 5, 36,
+	19, 2, 153, 151, 3, 2, 2, 2, 153, 152, 3, 2, 2, 2, 154, 29, 3, 2, 2, 2,
+	155, 156, 8, 16, 1, 2, 156, 158, 7, 25, 2, 2, 157, 159, 7, 25, 2, 2, 158,
+	157, 3, 2, 2, 2, 158, 159, 3, 2, 2, 2, 159, 160, 3, 2, 2, 2, 160, 164,
+	7, 6, 2, 2, 161, 163, 5, 38, 20, 2, 162, 161, 3, 2, 2, 2, 163, 166, 3,
+	2, 2, 2, 164, 162, 3, 2, 2, 2, 164, 165, 3, 2, 2, 2, 165, 167, 3, 2, 2,
+	2, 166, 164, 3, 2, 2, 2, 167, 168, 7, 7, 2, 2, 168, 31, 3, 2, 2, 2, 169,
+	170, 8, 17, 1, 2, 170, 172, 7, 25, 2, 2, 171, 173, 7, 3, 2, 2, 172, 171,
+	3, 2, 2, 2, 172, 173, 3, 2, 2, 2, 173, 174, 3, 2, 2, 2, 174, 175, 5, 44,
+	23, 2, 175, 33, 3, 2, 2, 2, 176, 177, 8, 18, 1, 2, 177, 179, 7, 25, 2,
+	2, 178, 180, 7, 25, 2, 2, 179, 178, 3, 2, 2, 2, 179, 180, 3, 2, 2, 2, 180,
+	181, 3, 2, 2, 2, 181, 185, 7, 6, 2, 2, 182, 184, 5, 38, 20, 2, 183, 182,
+	3, 2, 2, 2, 184, 187, 3, 2, 2, 2, 185, 183, 3, 2, 2, 2, 185, 186, 3, 2,
+	2, 2, 186, 188, 3, 2, 2, 2, 187, 185, 3, 2, 2, 2, 188, 189, 7, 7, 2, 2,
+	189, 35, 3, 2, 2, 2, 190, 191, 8, 19, 1, 2, 191, 193, 7, 25, 2, 2, 192,
+	194, 7, 3, 2, 2, 193, 192, 3, 2, 2, 2, 193, 194, 3, 2, 2, 2, 194, 195,
+	3, 2, 2, 2, 195, 196, 5, 44, 23, 2, 196, 37, 3, 2, 2, 2, 197, 198, 6, 20,
+	2, 2, 198, 201, 5, 40, 21, 2, 199, 201, 5, 42, 22, 2, 200, 197, 3, 2, 2,
+	2, 200, 199, 3, 2, 2, 2, 201, 39, 3, 2, 2, 2, 202, 203, 8, 21, 1, 2, 203,
+	204, 7, 25, 2, 2, 204, 206, 5, 44, 23, 2, 205, 207, 7, 23, 2, 2, 206, 205,
+	3, 2, 2, 2, 206, 207, 3, 2, 2, 2, 207, 41, 3, 2, 2, 2, 208, 210, 7, 8,
+	2, 2, 209, 208, 3, 2, 2, 2, 209, 210, 3, 2, 2, 2, 210, 211, 3, 2, 2, 2,
+	211, 212, 7, 25, 2, 2, 212, 43, 3, 2, 2, 2, 213, 214, 8, 23, 1, 2, 214,
+	222, 7, 25, 2, 2, 215, 222, 5, 48, 25, 2, 216, 222, 5, 50, 26, 2, 217,
+	222, 7, 17, 2, 2, 218, 222, 7, 9, 2, 2, 219, 222, 5, 46, 24, 2, 220, 222,
+	5, 30, 16, 2, 221, 213, 3, 2, 2, 2, 221, 215, 3, 2, 2, 2, 221, 216, 3,
+	2, 2, 2, 221, 217, 3, 2, 2, 2, 221, 218, 3, 2, 2, 2, 221, 219, 3, 2, 2,
+	2, 221, 220, 3, 2, 2, 2, 222, 45, 3, 2, 2, 2, 223, 224, 7, 8, 2, 2, 224,
+	225, 8, 24, 1, 2, 225, 226, 7, 25, 2, 2, 226, 47, 3, 2, 2, 2, 227, 228,
+	8, 25, 1, 2, 228, 229, 7, 25, 2, 2, 229, 230, 7, 10, 2, 2, 230, 231, 8,
+	25, 1, 2, 231, 232, 7, 25, 2, 2, 232, 233, 7, 11, 2, 2, 233, 234, 5, 44,
+	23, 2, 234, 49, 3, 2, 2, 2, 235, 236, 7, 10, 2, 2, 236, 237, 7, 11, 2,
+	2, 237, 238, 5, 44, 23, 2, 238, 51, 3, 2, 2, 2, 239, 241, 5, 54, 28, 2,
+	240, 239, 3, 2, 2, 2, 240, 241, 3, 2, 2, 2, 241, 242, 3, 2, 2, 2, 242,
+	243, 5, 56, 29, 2, 243, 53, 3, 2, 2, 2, 244, 245, 7, 18, 2, 2, 245, 247,
+	7, 4, 2, 2, 246, 248, 5, 70, 36, 2, 247, 246, 3, 2, 2, 2, 248, 249, 3,
+	2, 2, 2, 249, 247, 3, 2, 2, 2, 249, 250, 3, 2, 2, 2, 250, 251, 3, 2, 2,
+	2, 251, 252, 7, 5, 2, 2, 252, 55, 3, 2, 2, 2, 253, 254, 8, 29, 1, 2, 254,
+	255, 7, 25, 2, 2, 255, 256, 5, 72, 37, 2, 256, 260, 7, 6, 2, 2, 257, 259,
+	5, 58, 30, 2, 258, 257, 3, 2, 2, 2, 259, 262, 3, 2, 2, 2, 260, 258, 3,
+	2, 2, 2, 260, 261, 3, 2, 2, 2, 261, 263, 3, 2, 2, 2, 262, 260, 3, 2, 2,
+	2, 263, 264, 7, 7, 2, 2, 264, 57, 3, 2, 2, 2, 265, 267, 5, 60, 31, 2, 266,
+	265, 3, 2, 2, 2, 266, 267, 3, 2, 2, 2, 267, 270, 3, 2, 2, 2, 268, 271,
+	5, 54, 28, 2, 269, 271, 5, 62, 32, 2, 270, 268, 3, 2, 2, 2, 270, 269, 3,
+	2, 2, 2, 271, 272, 3, 2, 2, 2, 272, 273, 5, 64, 33, 2, 273, 59, 3, 2, 2,
+	2, 274, 276, 7, 15, 2, 2, 275, 277, 7, 4, 2, 2, 276, 275, 3, 2, 2, 2, 276,
+	277, 3, 2, 2, 2, 277, 284, 3, 2, 2, 2, 278, 280, 5, 70, 36, 2, 279, 278,
+	3, 2, 2, 2, 280, 281, 3, 2, 2, 2, 281, 279, 3, 2, 2, 2, 281, 282, 3, 2,
+	2, 2, 282, 285, 3, 2, 2, 2, 283, 285, 7, 22, 2, 2, 284, 279, 3, 2, 2, 2,
+	284, 283, 3, 2, 2, 2, 285, 287, 3, 2, 2, 2, 286, 288, 7, 5, 2, 2, 287,
+	286, 3, 2, 2, 2, 287, 288, 3, 2, 2, 2, 288, 61, 3, 2, 2, 2, 289, 290, 7,
+	16, 2, 2, 290, 291, 7, 25, 2, 2, 291, 63, 3, 2, 2, 2, 292, 293, 8, 33,
+	1, 2, 293, 294, 7, 25, 2, 2, 294, 296, 5, 74, 38, 2, 295, 297, 5, 66, 34,
+	2, 296, 295, 3, 2, 2, 2, 296, 297, 3, 2, 2, 2, 297, 299, 3, 2, 2, 2, 298,
+	300, 7, 25, 2, 2, 299, 298, 3, 2, 2, 2, 299, 300, 3, 2, 2, 2, 300, 302,
+	3, 2, 2, 2, 301, 303, 5, 68, 35, 2, 302, 301, 3, 2, 2, 2, 302, 303, 3,
+	2, 2, 2, 303, 65, 3, 2, 2, 2, 304, 306, 7, 4, 2, 2, 305, 307, 7, 25, 2,
+	2, 306, 305, 3, 2, 2, 2, 306, 307, 3, 2, 2, 2, 307, 308, 3, 2, 2, 2, 308,
+	309, 7, 5, 2, 2, 309, 67, 3, 2, 2, 2, 310, 312, 7, 4, 2, 2, 311, 313, 5,
+	44, 23, 2, 312, 311, 3, 2, 2, 2, 312, 313, 3, 2, 2, 2, 313, 314, 3, 2,
+	2, 2, 314, 315, 7, 5, 2, 2, 315, 69, 3, 2, 2, 2, 316, 317, 7, 25, 2, 2,
+	317, 318, 8, 36, 1, 2, 318, 319, 7, 24, 2, 2, 319, 71, 3, 2, 2, 2, 320,
+	322, 7, 25, 2, 2, 321, 323, 7, 12, 2, 2, 322, 321, 3, 2, 2, 2, 322, 323,
+	3, 2, 2, 2, 323, 325, 3, 2, 2, 2, 324, 320, 3, 2, 2, 2, 325, 326, 3, 2,
+	2, 2, 326, 324, 3, 2, 2, 2, 326, 327, 3, 2, 2, 2, 327, 73, 3, 2, 2, 2,
+	328, 329, 7, 13, 2, 2, 329, 334, 7, 25, 2, 2, 330, 331, 7, 12, 2, 2, 331,
+	333, 7, 25, 2, 2, 332, 330, 3, 2, 2, 2, 333, 336, 3, 2, 2, 2, 334, 332,
+	3, 2, 2, 2, 334, 335, 3, 2, 2, 2, 335, 344, 3, 2, 2, 2, 336, 334, 3, 2,
+	2, 2, 337, 338, 7, 14, 2, 2, 338, 341, 7, 25, 2, 2, 339, 340, 7, 12, 2,
+	2, 340, 342, 7, 25, 2, 2, 341, 339, 3, 2, 2, 2, 341, 342, 3, 2, 2, 2, 342,
+	344, 3, 2, 2, 2, 343, 328, 3, 2, 2, 2, 343, 337, 3, 2, 2, 2, 344, 345,
+	3, 2, 2, 2, 345, 343, 3, 2, 2, 2, 345, 346, 3, 2, 2, 2, 346, 75, 3, 2,
+	2, 2, 41, 79, 87, 97, 109, 124, 130, 142, 149, 153, 158, 164, 172, 179,
+	185, 193, 200, 206, 209, 221, 240, 249, 260, 266, 270, 276, 281, 284, 287,
+	296, 299, 302, 306, 312, 322, 326, 334, 341, 343, 345,
+}
+var literalNames = []string{
+	"", "'='", "'('", "')'", "'{'", "'}'", "'*'", "'time.Time'", "'['", "']'",
+	"'-'", "'/'", "'/:'", "'@doc'", "'@handler'", "'interface{}'", "'@server'",
+}
+var symbolicNames = []string{
+	"", "", "", "", "", "", "", "", "", "", "", "", "", "ATDOC", "ATHANDLER",
+	"INTERFACE", "ATSERVER", "WS", "COMMENT", "LINE_COMMENT", "STRING", "RAW_STRING",
+	"LINE_VALUE", "ID",
+}
+
+var ruleNames = []string{
+	"api", "spec", "syntaxLit", "importSpec", "importLit", "importBlock", "importBlockValue",
+	"importValue", "infoSpec", "typeSpec", "typeLit", "typeBlock", "typeLitBody",
+	"typeBlockBody", "typeStruct", "typeAlias", "typeBlockStruct", "typeBlockAlias",
+	"field", "normalField", "anonymousFiled", "dataType", "pointerType", "mapType",
+	"arrayType", "serviceSpec", "atServer", "serviceApi", "serviceRoute", "atDoc",
+	"atHandler", "route", "body", "replybody", "kvLit", "serviceName", "path",
+}
+
+type ApiParserParser struct {
+	*antlr.BaseParser
+}
+
+// NewApiParserParser produces a new parser instance for the optional input antlr.TokenStream.
+//
+// The *ApiParserParser instance produced may be reused by calling the SetInputStream method.
+// The initial parser configuration is expensive to construct, and the object is not thread-safe;
+// however, if used within a Golang sync.Pool, the construction cost amortizes well and the
+// objects can be used in a thread-safe manner.
+func NewApiParserParser(input antlr.TokenStream) *ApiParserParser {
+	this := new(ApiParserParser)
+	deserializer := antlr.NewATNDeserializer(nil)
+	deserializedATN := deserializer.DeserializeFromUInt16(parserATN)
+	decisionToDFA := make([]*antlr.DFA, len(deserializedATN.DecisionToState))
+	for index, ds := range deserializedATN.DecisionToState {
+		decisionToDFA[index] = antlr.NewDFA(ds, index)
+	}
+	this.BaseParser = antlr.NewBaseParser(input)
+
+	this.Interpreter = antlr.NewParserATNSimulator(this, deserializedATN, decisionToDFA, antlr.NewPredictionContextCache())
+	this.RuleNames = ruleNames
+	this.LiteralNames = literalNames
+	this.SymbolicNames = symbolicNames
+	this.GrammarFileName = "ApiParser.g4"
+
+	return this
+}
+
+// ApiParserParser tokens.
+const (
+	ApiParserParserEOF          = antlr.TokenEOF
+	ApiParserParserT__0         = 1
+	ApiParserParserT__1         = 2
+	ApiParserParserT__2         = 3
+	ApiParserParserT__3         = 4
+	ApiParserParserT__4         = 5
+	ApiParserParserT__5         = 6
+	ApiParserParserT__6         = 7
+	ApiParserParserT__7         = 8
+	ApiParserParserT__8         = 9
+	ApiParserParserT__9         = 10
+	ApiParserParserT__10        = 11
+	ApiParserParserT__11        = 12
+	ApiParserParserATDOC        = 13
+	ApiParserParserATHANDLER    = 14
+	ApiParserParserINTERFACE    = 15
+	ApiParserParserATSERVER     = 16
+	ApiParserParserWS           = 17
+	ApiParserParserCOMMENT      = 18
+	ApiParserParserLINE_COMMENT = 19
+	ApiParserParserSTRING       = 20
+	ApiParserParserRAW_STRING   = 21
+	ApiParserParserLINE_VALUE   = 22
+	ApiParserParserID           = 23
+)
+
+// ApiParserParser rules.
+const (
+	ApiParserParserRULE_api              = 0
+	ApiParserParserRULE_spec             = 1
+	ApiParserParserRULE_syntaxLit        = 2
+	ApiParserParserRULE_importSpec       = 3
+	ApiParserParserRULE_importLit        = 4
+	ApiParserParserRULE_importBlock      = 5
+	ApiParserParserRULE_importBlockValue = 6
+	ApiParserParserRULE_importValue      = 7
+	ApiParserParserRULE_infoSpec         = 8
+	ApiParserParserRULE_typeSpec         = 9
+	ApiParserParserRULE_typeLit          = 10
+	ApiParserParserRULE_typeBlock        = 11
+	ApiParserParserRULE_typeLitBody      = 12
+	ApiParserParserRULE_typeBlockBody    = 13
+	ApiParserParserRULE_typeStruct       = 14
+	ApiParserParserRULE_typeAlias        = 15
+	ApiParserParserRULE_typeBlockStruct  = 16
+	ApiParserParserRULE_typeBlockAlias   = 17
+	ApiParserParserRULE_field            = 18
+	ApiParserParserRULE_normalField      = 19
+	ApiParserParserRULE_anonymousFiled   = 20
+	ApiParserParserRULE_dataType         = 21
+	ApiParserParserRULE_pointerType      = 22
+	ApiParserParserRULE_mapType          = 23
+	ApiParserParserRULE_arrayType        = 24
+	ApiParserParserRULE_serviceSpec      = 25
+	ApiParserParserRULE_atServer         = 26
+	ApiParserParserRULE_serviceApi       = 27
+	ApiParserParserRULE_serviceRoute     = 28
+	ApiParserParserRULE_atDoc            = 29
+	ApiParserParserRULE_atHandler        = 30
+	ApiParserParserRULE_route            = 31
+	ApiParserParserRULE_body             = 32
+	ApiParserParserRULE_replybody        = 33
+	ApiParserParserRULE_kvLit            = 34
+	ApiParserParserRULE_serviceName      = 35
+	ApiParserParserRULE_path             = 36
+)
+
+// IApiContext is an interface to support dynamic dispatch.
+type IApiContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsApiContext differentiates from other interfaces.
+	IsApiContext()
+}
+
+type ApiContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyApiContext() *ApiContext {
+	var p = new(ApiContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_api
+	return p
+}
+
+func (*ApiContext) IsApiContext() {}
+
+func NewApiContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ApiContext {
+	var p = new(ApiContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_api
+
+	return p
+}
+
+func (s *ApiContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ApiContext) AllSpec() []ISpecContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*ISpecContext)(nil)).Elem())
+	var tst = make([]ISpecContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(ISpecContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *ApiContext) Spec(i int) ISpecContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ISpecContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ISpecContext)
+}
+
+func (s *ApiContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ApiContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ApiContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitApi(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) Api() (localctx IApiContext) {
+	localctx = NewApiContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 0, ApiParserParserRULE_api)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	p.SetState(77)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	for _la == ApiParserParserATSERVER || _la == ApiParserParserID {
+		{
+			p.SetState(74)
+			p.Spec()
+		}
+
+		p.SetState(79)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+	}
+
+	return localctx
+}
+
+// ISpecContext is an interface to support dynamic dispatch.
+type ISpecContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsSpecContext differentiates from other interfaces.
+	IsSpecContext()
+}
+
+type SpecContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptySpecContext() *SpecContext {
+	var p = new(SpecContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_spec
+	return p
+}
+
+func (*SpecContext) IsSpecContext() {}
+
+func NewSpecContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SpecContext {
+	var p = new(SpecContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_spec
+
+	return p
+}
+
+func (s *SpecContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *SpecContext) SyntaxLit() ISyntaxLitContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ISyntaxLitContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ISyntaxLitContext)
+}
+
+func (s *SpecContext) ImportSpec() IImportSpecContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IImportSpecContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IImportSpecContext)
+}
+
+func (s *SpecContext) InfoSpec() IInfoSpecContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IInfoSpecContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IInfoSpecContext)
+}
+
+func (s *SpecContext) TypeSpec() ITypeSpecContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeSpecContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeSpecContext)
+}
+
+func (s *SpecContext) ServiceSpec() IServiceSpecContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IServiceSpecContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IServiceSpecContext)
+}
+
+func (s *SpecContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *SpecContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *SpecContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitSpec(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) Spec() (localctx ISpecContext) {
+	localctx = NewSpecContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 2, ApiParserParserRULE_spec)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(85)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 1, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		{
+			p.SetState(80)
+			p.SyntaxLit()
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(81)
+			p.ImportSpec()
+		}
+
+	case 3:
+		p.EnterOuterAlt(localctx, 3)
+		{
+			p.SetState(82)
+			p.InfoSpec()
+		}
+
+	case 4:
+		p.EnterOuterAlt(localctx, 4)
+		{
+			p.SetState(83)
+			p.TypeSpec()
+		}
+
+	case 5:
+		p.EnterOuterAlt(localctx, 5)
+		{
+			p.SetState(84)
+			p.ServiceSpec()
+		}
+
+	}
+
+	return localctx
+}
+
+// ISyntaxLitContext is an interface to support dynamic dispatch.
+type ISyntaxLitContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetSyntaxToken returns the syntaxToken token.
+	GetSyntaxToken() antlr.Token
+
+	// GetAssign returns the assign token.
+	GetAssign() antlr.Token
+
+	// GetVersion returns the version token.
+	GetVersion() antlr.Token
+
+	// SetSyntaxToken sets the syntaxToken token.
+	SetSyntaxToken(antlr.Token)
+
+	// SetAssign sets the assign token.
+	SetAssign(antlr.Token)
+
+	// SetVersion sets the version token.
+	SetVersion(antlr.Token)
+
+	// IsSyntaxLitContext differentiates from other interfaces.
+	IsSyntaxLitContext()
+}
+
+type SyntaxLitContext struct {
+	*antlr.BaseParserRuleContext
+	parser      antlr.Parser
+	syntaxToken antlr.Token
+	assign      antlr.Token
+	version     antlr.Token
+}
+
+func NewEmptySyntaxLitContext() *SyntaxLitContext {
+	var p = new(SyntaxLitContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_syntaxLit
+	return p
+}
+
+func (*SyntaxLitContext) IsSyntaxLitContext() {}
+
+func NewSyntaxLitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SyntaxLitContext {
+	var p = new(SyntaxLitContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_syntaxLit
+
+	return p
+}
+
+func (s *SyntaxLitContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *SyntaxLitContext) GetSyntaxToken() antlr.Token { return s.syntaxToken }
+
+func (s *SyntaxLitContext) GetAssign() antlr.Token { return s.assign }
+
+func (s *SyntaxLitContext) GetVersion() antlr.Token { return s.version }
+
+func (s *SyntaxLitContext) SetSyntaxToken(v antlr.Token) { s.syntaxToken = v }
+
+func (s *SyntaxLitContext) SetAssign(v antlr.Token) { s.assign = v }
+
+func (s *SyntaxLitContext) SetVersion(v antlr.Token) { s.version = v }
+
+func (s *SyntaxLitContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *SyntaxLitContext) STRING() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserSTRING, 0)
+}
+
+func (s *SyntaxLitContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *SyntaxLitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *SyntaxLitContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitSyntaxLit(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) SyntaxLit() (localctx ISyntaxLitContext) {
+	localctx = NewSyntaxLitContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 4, ApiParserParserRULE_syntaxLit)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	match(p, "syntax")
+	{
+		p.SetState(88)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*SyntaxLitContext).syntaxToken = _m
+	}
+	{
+		p.SetState(89)
+
+		var _m = p.Match(ApiParserParserT__0)
+
+		localctx.(*SyntaxLitContext).assign = _m
+	}
+	checkVersion(p)
+	{
+		p.SetState(91)
+
+		var _m = p.Match(ApiParserParserSTRING)
+
+		localctx.(*SyntaxLitContext).version = _m
+	}
+
+	return localctx
+}
+
+// IImportSpecContext is an interface to support dynamic dispatch.
+type IImportSpecContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsImportSpecContext differentiates from other interfaces.
+	IsImportSpecContext()
+}
+
+type ImportSpecContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyImportSpecContext() *ImportSpecContext {
+	var p = new(ImportSpecContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_importSpec
+	return p
+}
+
+func (*ImportSpecContext) IsImportSpecContext() {}
+
+func NewImportSpecContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ImportSpecContext {
+	var p = new(ImportSpecContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_importSpec
+
+	return p
+}
+
+func (s *ImportSpecContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ImportSpecContext) ImportLit() IImportLitContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IImportLitContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IImportLitContext)
+}
+
+func (s *ImportSpecContext) ImportBlock() IImportBlockContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IImportBlockContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IImportBlockContext)
+}
+
+func (s *ImportSpecContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ImportSpecContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ImportSpecContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitImportSpec(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ImportSpec() (localctx IImportSpecContext) {
+	localctx = NewImportSpecContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 6, ApiParserParserRULE_importSpec)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(95)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 2, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		{
+			p.SetState(93)
+			p.ImportLit()
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(94)
+			p.ImportBlock()
+		}
+
+	}
+
+	return localctx
+}
+
+// IImportLitContext is an interface to support dynamic dispatch.
+type IImportLitContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetImportToken returns the importToken token.
+	GetImportToken() antlr.Token
+
+	// SetImportToken sets the importToken token.
+	SetImportToken(antlr.Token)
+
+	// IsImportLitContext differentiates from other interfaces.
+	IsImportLitContext()
+}
+
+type ImportLitContext struct {
+	*antlr.BaseParserRuleContext
+	parser      antlr.Parser
+	importToken antlr.Token
+}
+
+func NewEmptyImportLitContext() *ImportLitContext {
+	var p = new(ImportLitContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_importLit
+	return p
+}
+
+func (*ImportLitContext) IsImportLitContext() {}
+
+func NewImportLitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ImportLitContext {
+	var p = new(ImportLitContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_importLit
+
+	return p
+}
+
+func (s *ImportLitContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ImportLitContext) GetImportToken() antlr.Token { return s.importToken }
+
+func (s *ImportLitContext) SetImportToken(v antlr.Token) { s.importToken = v }
+
+func (s *ImportLitContext) ImportValue() IImportValueContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IImportValueContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IImportValueContext)
+}
+
+func (s *ImportLitContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *ImportLitContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ImportLitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ImportLitContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitImportLit(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ImportLit() (localctx IImportLitContext) {
+	localctx = NewImportLitContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 8, ApiParserParserRULE_importLit)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	match(p, "import")
+	{
+		p.SetState(98)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*ImportLitContext).importToken = _m
+	}
+	{
+		p.SetState(99)
+		p.ImportValue()
+	}
+
+	return localctx
+}
+
+// IImportBlockContext is an interface to support dynamic dispatch.
+type IImportBlockContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetImportToken returns the importToken token.
+	GetImportToken() antlr.Token
+
+	// SetImportToken sets the importToken token.
+	SetImportToken(antlr.Token)
+
+	// IsImportBlockContext differentiates from other interfaces.
+	IsImportBlockContext()
+}
+
+type ImportBlockContext struct {
+	*antlr.BaseParserRuleContext
+	parser      antlr.Parser
+	importToken antlr.Token
+}
+
+func NewEmptyImportBlockContext() *ImportBlockContext {
+	var p = new(ImportBlockContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_importBlock
+	return p
+}
+
+func (*ImportBlockContext) IsImportBlockContext() {}
+
+func NewImportBlockContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ImportBlockContext {
+	var p = new(ImportBlockContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_importBlock
+
+	return p
+}
+
+func (s *ImportBlockContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ImportBlockContext) GetImportToken() antlr.Token { return s.importToken }
+
+func (s *ImportBlockContext) SetImportToken(v antlr.Token) { s.importToken = v }
+
+func (s *ImportBlockContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *ImportBlockContext) AllImportBlockValue() []IImportBlockValueContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*IImportBlockValueContext)(nil)).Elem())
+	var tst = make([]IImportBlockValueContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(IImportBlockValueContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *ImportBlockContext) ImportBlockValue(i int) IImportBlockValueContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IImportBlockValueContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IImportBlockValueContext)
+}
+
+func (s *ImportBlockContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ImportBlockContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ImportBlockContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitImportBlock(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ImportBlock() (localctx IImportBlockContext) {
+	localctx = NewImportBlockContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 10, ApiParserParserRULE_importBlock)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	match(p, "import")
+	{
+		p.SetState(102)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*ImportBlockContext).importToken = _m
+	}
+	{
+		p.SetState(103)
+		p.Match(ApiParserParserT__1)
+	}
+	p.SetState(105)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	for ok := true; ok; ok = _la == ApiParserParserSTRING {
+		{
+			p.SetState(104)
+			p.ImportBlockValue()
+		}
+
+		p.SetState(107)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+	}
+	{
+		p.SetState(109)
+		p.Match(ApiParserParserT__2)
+	}
+
+	return localctx
+}
+
+// IImportBlockValueContext is an interface to support dynamic dispatch.
+type IImportBlockValueContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsImportBlockValueContext differentiates from other interfaces.
+	IsImportBlockValueContext()
+}
+
+type ImportBlockValueContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyImportBlockValueContext() *ImportBlockValueContext {
+	var p = new(ImportBlockValueContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_importBlockValue
+	return p
+}
+
+func (*ImportBlockValueContext) IsImportBlockValueContext() {}
+
+func NewImportBlockValueContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ImportBlockValueContext {
+	var p = new(ImportBlockValueContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_importBlockValue
+
+	return p
+}
+
+func (s *ImportBlockValueContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ImportBlockValueContext) ImportValue() IImportValueContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IImportValueContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IImportValueContext)
+}
+
+func (s *ImportBlockValueContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ImportBlockValueContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ImportBlockValueContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitImportBlockValue(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ImportBlockValue() (localctx IImportBlockValueContext) {
+	localctx = NewImportBlockValueContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 12, ApiParserParserRULE_importBlockValue)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(111)
+		p.ImportValue()
+	}
+
+	return localctx
+}
+
+// IImportValueContext is an interface to support dynamic dispatch.
+type IImportValueContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsImportValueContext differentiates from other interfaces.
+	IsImportValueContext()
+}
+
+type ImportValueContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyImportValueContext() *ImportValueContext {
+	var p = new(ImportValueContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_importValue
+	return p
+}
+
+func (*ImportValueContext) IsImportValueContext() {}
+
+func NewImportValueContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ImportValueContext {
+	var p = new(ImportValueContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_importValue
+
+	return p
+}
+
+func (s *ImportValueContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ImportValueContext) STRING() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserSTRING, 0)
+}
+
+func (s *ImportValueContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ImportValueContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ImportValueContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitImportValue(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ImportValue() (localctx IImportValueContext) {
+	localctx = NewImportValueContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 14, ApiParserParserRULE_importValue)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	checkImportValue(p)
+	{
+		p.SetState(114)
+		p.Match(ApiParserParserSTRING)
+	}
+
+	return localctx
+}
+
+// IInfoSpecContext is an interface to support dynamic dispatch.
+type IInfoSpecContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetInfoToken returns the infoToken token.
+	GetInfoToken() antlr.Token
+
+	// GetLp returns the lp token.
+	GetLp() antlr.Token
+
+	// GetRp returns the rp token.
+	GetRp() antlr.Token
+
+	// SetInfoToken sets the infoToken token.
+	SetInfoToken(antlr.Token)
+
+	// SetLp sets the lp token.
+	SetLp(antlr.Token)
+
+	// SetRp sets the rp token.
+	SetRp(antlr.Token)
+
+	// IsInfoSpecContext differentiates from other interfaces.
+	IsInfoSpecContext()
+}
+
+type InfoSpecContext struct {
+	*antlr.BaseParserRuleContext
+	parser    antlr.Parser
+	infoToken antlr.Token
+	lp        antlr.Token
+	rp        antlr.Token
+}
+
+func NewEmptyInfoSpecContext() *InfoSpecContext {
+	var p = new(InfoSpecContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_infoSpec
+	return p
+}
+
+func (*InfoSpecContext) IsInfoSpecContext() {}
+
+func NewInfoSpecContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *InfoSpecContext {
+	var p = new(InfoSpecContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_infoSpec
+
+	return p
+}
+
+func (s *InfoSpecContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *InfoSpecContext) GetInfoToken() antlr.Token { return s.infoToken }
+
+func (s *InfoSpecContext) GetLp() antlr.Token { return s.lp }
+
+func (s *InfoSpecContext) GetRp() antlr.Token { return s.rp }
+
+func (s *InfoSpecContext) SetInfoToken(v antlr.Token) { s.infoToken = v }
+
+func (s *InfoSpecContext) SetLp(v antlr.Token) { s.lp = v }
+
+func (s *InfoSpecContext) SetRp(v antlr.Token) { s.rp = v }
+
+func (s *InfoSpecContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *InfoSpecContext) AllKvLit() []IKvLitContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*IKvLitContext)(nil)).Elem())
+	var tst = make([]IKvLitContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(IKvLitContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *InfoSpecContext) KvLit(i int) IKvLitContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IKvLitContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IKvLitContext)
+}
+
+func (s *InfoSpecContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *InfoSpecContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *InfoSpecContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitInfoSpec(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) InfoSpec() (localctx IInfoSpecContext) {
+	localctx = NewInfoSpecContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 16, ApiParserParserRULE_infoSpec)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	match(p, "info")
+	{
+		p.SetState(117)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*InfoSpecContext).infoToken = _m
+	}
+	{
+		p.SetState(118)
+
+		var _m = p.Match(ApiParserParserT__1)
+
+		localctx.(*InfoSpecContext).lp = _m
+	}
+	p.SetState(120)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	for ok := true; ok; ok = _la == ApiParserParserID {
+		{
+			p.SetState(119)
+			p.KvLit()
+		}
+
+		p.SetState(122)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+	}
+	{
+		p.SetState(124)
+
+		var _m = p.Match(ApiParserParserT__2)
+
+		localctx.(*InfoSpecContext).rp = _m
+	}
+
+	return localctx
+}
+
+// ITypeSpecContext is an interface to support dynamic dispatch.
+type ITypeSpecContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsTypeSpecContext differentiates from other interfaces.
+	IsTypeSpecContext()
+}
+
+type TypeSpecContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyTypeSpecContext() *TypeSpecContext {
+	var p = new(TypeSpecContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_typeSpec
+	return p
+}
+
+func (*TypeSpecContext) IsTypeSpecContext() {}
+
+func NewTypeSpecContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeSpecContext {
+	var p = new(TypeSpecContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_typeSpec
+
+	return p
+}
+
+func (s *TypeSpecContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TypeSpecContext) TypeLit() ITypeLitContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeLitContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeLitContext)
+}
+
+func (s *TypeSpecContext) TypeBlock() ITypeBlockContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeBlockContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeBlockContext)
+}
+
+func (s *TypeSpecContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TypeSpecContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *TypeSpecContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitTypeSpec(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) TypeSpec() (localctx ITypeSpecContext) {
+	localctx = NewTypeSpecContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 18, ApiParserParserRULE_typeSpec)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(128)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 5, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		{
+			p.SetState(126)
+			p.TypeLit()
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(127)
+			p.TypeBlock()
+		}
+
+	}
+
+	return localctx
+}
+
+// ITypeLitContext is an interface to support dynamic dispatch.
+type ITypeLitContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetTypeToken returns the typeToken token.
+	GetTypeToken() antlr.Token
+
+	// SetTypeToken sets the typeToken token.
+	SetTypeToken(antlr.Token)
+
+	// IsTypeLitContext differentiates from other interfaces.
+	IsTypeLitContext()
+}
+
+type TypeLitContext struct {
+	*antlr.BaseParserRuleContext
+	parser    antlr.Parser
+	typeToken antlr.Token
+}
+
+func NewEmptyTypeLitContext() *TypeLitContext {
+	var p = new(TypeLitContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_typeLit
+	return p
+}
+
+func (*TypeLitContext) IsTypeLitContext() {}
+
+func NewTypeLitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeLitContext {
+	var p = new(TypeLitContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_typeLit
+
+	return p
+}
+
+func (s *TypeLitContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TypeLitContext) GetTypeToken() antlr.Token { return s.typeToken }
+
+func (s *TypeLitContext) SetTypeToken(v antlr.Token) { s.typeToken = v }
+
+func (s *TypeLitContext) TypeLitBody() ITypeLitBodyContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeLitBodyContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeLitBodyContext)
+}
+
+func (s *TypeLitContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *TypeLitContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TypeLitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *TypeLitContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitTypeLit(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) TypeLit() (localctx ITypeLitContext) {
+	localctx = NewTypeLitContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 20, ApiParserParserRULE_typeLit)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	match(p, "type")
+	{
+		p.SetState(131)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*TypeLitContext).typeToken = _m
+	}
+	{
+		p.SetState(132)
+		p.TypeLitBody()
+	}
+
+	return localctx
+}
+
+// ITypeBlockContext is an interface to support dynamic dispatch.
+type ITypeBlockContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetTypeToken returns the typeToken token.
+	GetTypeToken() antlr.Token
+
+	// GetLp returns the lp token.
+	GetLp() antlr.Token
+
+	// GetRp returns the rp token.
+	GetRp() antlr.Token
+
+	// SetTypeToken sets the typeToken token.
+	SetTypeToken(antlr.Token)
+
+	// SetLp sets the lp token.
+	SetLp(antlr.Token)
+
+	// SetRp sets the rp token.
+	SetRp(antlr.Token)
+
+	// IsTypeBlockContext differentiates from other interfaces.
+	IsTypeBlockContext()
+}
+
+type TypeBlockContext struct {
+	*antlr.BaseParserRuleContext
+	parser    antlr.Parser
+	typeToken antlr.Token
+	lp        antlr.Token
+	rp        antlr.Token
+}
+
+func NewEmptyTypeBlockContext() *TypeBlockContext {
+	var p = new(TypeBlockContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_typeBlock
+	return p
+}
+
+func (*TypeBlockContext) IsTypeBlockContext() {}
+
+func NewTypeBlockContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeBlockContext {
+	var p = new(TypeBlockContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_typeBlock
+
+	return p
+}
+
+func (s *TypeBlockContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TypeBlockContext) GetTypeToken() antlr.Token { return s.typeToken }
+
+func (s *TypeBlockContext) GetLp() antlr.Token { return s.lp }
+
+func (s *TypeBlockContext) GetRp() antlr.Token { return s.rp }
+
+func (s *TypeBlockContext) SetTypeToken(v antlr.Token) { s.typeToken = v }
+
+func (s *TypeBlockContext) SetLp(v antlr.Token) { s.lp = v }
+
+func (s *TypeBlockContext) SetRp(v antlr.Token) { s.rp = v }
+
+func (s *TypeBlockContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *TypeBlockContext) AllTypeBlockBody() []ITypeBlockBodyContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*ITypeBlockBodyContext)(nil)).Elem())
+	var tst = make([]ITypeBlockBodyContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(ITypeBlockBodyContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *TypeBlockContext) TypeBlockBody(i int) ITypeBlockBodyContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeBlockBodyContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeBlockBodyContext)
+}
+
+func (s *TypeBlockContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TypeBlockContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *TypeBlockContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitTypeBlock(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) TypeBlock() (localctx ITypeBlockContext) {
+	localctx = NewTypeBlockContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 22, ApiParserParserRULE_typeBlock)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	match(p, "type")
+	{
+		p.SetState(135)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*TypeBlockContext).typeToken = _m
+	}
+	{
+		p.SetState(136)
+
+		var _m = p.Match(ApiParserParserT__1)
+
+		localctx.(*TypeBlockContext).lp = _m
+	}
+	p.SetState(140)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	for _la == ApiParserParserID {
+		{
+			p.SetState(137)
+			p.TypeBlockBody()
+		}
+
+		p.SetState(142)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+	}
+	{
+		p.SetState(143)
+
+		var _m = p.Match(ApiParserParserT__2)
+
+		localctx.(*TypeBlockContext).rp = _m
+	}
+
+	return localctx
+}
+
+// ITypeLitBodyContext is an interface to support dynamic dispatch.
+type ITypeLitBodyContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsTypeLitBodyContext differentiates from other interfaces.
+	IsTypeLitBodyContext()
+}
+
+type TypeLitBodyContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyTypeLitBodyContext() *TypeLitBodyContext {
+	var p = new(TypeLitBodyContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_typeLitBody
+	return p
+}
+
+func (*TypeLitBodyContext) IsTypeLitBodyContext() {}
+
+func NewTypeLitBodyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeLitBodyContext {
+	var p = new(TypeLitBodyContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_typeLitBody
+
+	return p
+}
+
+func (s *TypeLitBodyContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TypeLitBodyContext) TypeStruct() ITypeStructContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeStructContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeStructContext)
+}
+
+func (s *TypeLitBodyContext) TypeAlias() ITypeAliasContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeAliasContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeAliasContext)
+}
+
+func (s *TypeLitBodyContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TypeLitBodyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *TypeLitBodyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitTypeLitBody(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) TypeLitBody() (localctx ITypeLitBodyContext) {
+	localctx = NewTypeLitBodyContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 24, ApiParserParserRULE_typeLitBody)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(147)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 7, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		{
+			p.SetState(145)
+			p.TypeStruct()
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(146)
+			p.TypeAlias()
+		}
+
+	}
+
+	return localctx
+}
+
+// ITypeBlockBodyContext is an interface to support dynamic dispatch.
+type ITypeBlockBodyContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsTypeBlockBodyContext differentiates from other interfaces.
+	IsTypeBlockBodyContext()
+}
+
+type TypeBlockBodyContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyTypeBlockBodyContext() *TypeBlockBodyContext {
+	var p = new(TypeBlockBodyContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_typeBlockBody
+	return p
+}
+
+func (*TypeBlockBodyContext) IsTypeBlockBodyContext() {}
+
+func NewTypeBlockBodyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeBlockBodyContext {
+	var p = new(TypeBlockBodyContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_typeBlockBody
+
+	return p
+}
+
+func (s *TypeBlockBodyContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TypeBlockBodyContext) TypeBlockStruct() ITypeBlockStructContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeBlockStructContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeBlockStructContext)
+}
+
+func (s *TypeBlockBodyContext) TypeBlockAlias() ITypeBlockAliasContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeBlockAliasContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeBlockAliasContext)
+}
+
+func (s *TypeBlockBodyContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TypeBlockBodyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *TypeBlockBodyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitTypeBlockBody(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) TypeBlockBody() (localctx ITypeBlockBodyContext) {
+	localctx = NewTypeBlockBodyContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 26, ApiParserParserRULE_typeBlockBody)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(151)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 8, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		{
+			p.SetState(149)
+			p.TypeBlockStruct()
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(150)
+			p.TypeBlockAlias()
+		}
+
+	}
+
+	return localctx
+}
+
+// ITypeStructContext is an interface to support dynamic dispatch.
+type ITypeStructContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetStructName returns the structName token.
+	GetStructName() antlr.Token
+
+	// GetStructToken returns the structToken token.
+	GetStructToken() antlr.Token
+
+	// GetLbrace returns the lbrace token.
+	GetLbrace() antlr.Token
+
+	// GetRbrace returns the rbrace token.
+	GetRbrace() antlr.Token
+
+	// SetStructName sets the structName token.
+	SetStructName(antlr.Token)
+
+	// SetStructToken sets the structToken token.
+	SetStructToken(antlr.Token)
+
+	// SetLbrace sets the lbrace token.
+	SetLbrace(antlr.Token)
+
+	// SetRbrace sets the rbrace token.
+	SetRbrace(antlr.Token)
+
+	// IsTypeStructContext differentiates from other interfaces.
+	IsTypeStructContext()
+}
+
+type TypeStructContext struct {
+	*antlr.BaseParserRuleContext
+	parser      antlr.Parser
+	structName  antlr.Token
+	structToken antlr.Token
+	lbrace      antlr.Token
+	rbrace      antlr.Token
+}
+
+func NewEmptyTypeStructContext() *TypeStructContext {
+	var p = new(TypeStructContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_typeStruct
+	return p
+}
+
+func (*TypeStructContext) IsTypeStructContext() {}
+
+func NewTypeStructContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeStructContext {
+	var p = new(TypeStructContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_typeStruct
+
+	return p
+}
+
+func (s *TypeStructContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TypeStructContext) GetStructName() antlr.Token { return s.structName }
+
+func (s *TypeStructContext) GetStructToken() antlr.Token { return s.structToken }
+
+func (s *TypeStructContext) GetLbrace() antlr.Token { return s.lbrace }
+
+func (s *TypeStructContext) GetRbrace() antlr.Token { return s.rbrace }
+
+func (s *TypeStructContext) SetStructName(v antlr.Token) { s.structName = v }
+
+func (s *TypeStructContext) SetStructToken(v antlr.Token) { s.structToken = v }
+
+func (s *TypeStructContext) SetLbrace(v antlr.Token) { s.lbrace = v }
+
+func (s *TypeStructContext) SetRbrace(v antlr.Token) { s.rbrace = v }
+
+func (s *TypeStructContext) AllID() []antlr.TerminalNode {
+	return s.GetTokens(ApiParserParserID)
+}
+
+func (s *TypeStructContext) ID(i int) antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, i)
+}
+
+func (s *TypeStructContext) AllField() []IFieldContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*IFieldContext)(nil)).Elem())
+	var tst = make([]IFieldContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(IFieldContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *TypeStructContext) Field(i int) IFieldContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IFieldContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IFieldContext)
+}
+
+func (s *TypeStructContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TypeStructContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *TypeStructContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitTypeStruct(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) TypeStruct() (localctx ITypeStructContext) {
+	localctx = NewTypeStructContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 28, ApiParserParserRULE_typeStruct)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	var _alt int
+
+	p.EnterOuterAlt(localctx, 1)
+	checkKeyword(p)
+	{
+		p.SetState(154)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*TypeStructContext).structName = _m
+	}
+	p.SetState(156)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserID {
+		{
+			p.SetState(155)
+
+			var _m = p.Match(ApiParserParserID)
+
+			localctx.(*TypeStructContext).structToken = _m
+		}
+
+	}
+	{
+		p.SetState(158)
+
+		var _m = p.Match(ApiParserParserT__3)
+
+		localctx.(*TypeStructContext).lbrace = _m
+	}
+	p.SetState(162)
+	p.GetErrorHandler().Sync(p)
+	_alt = p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 10, p.GetParserRuleContext())
+
+	for _alt != 2 && _alt != antlr.ATNInvalidAltNumber {
+		if _alt == 1 {
+			{
+				p.SetState(159)
+				p.Field()
+			}
+
+		}
+		p.SetState(164)
+		p.GetErrorHandler().Sync(p)
+		_alt = p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 10, p.GetParserRuleContext())
+	}
+	{
+		p.SetState(165)
+
+		var _m = p.Match(ApiParserParserT__4)
+
+		localctx.(*TypeStructContext).rbrace = _m
+	}
+
+	return localctx
+}
+
+// ITypeAliasContext is an interface to support dynamic dispatch.
+type ITypeAliasContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetAlias returns the alias token.
+	GetAlias() antlr.Token
+
+	// GetAssign returns the assign token.
+	GetAssign() antlr.Token
+
+	// SetAlias sets the alias token.
+	SetAlias(antlr.Token)
+
+	// SetAssign sets the assign token.
+	SetAssign(antlr.Token)
+
+	// IsTypeAliasContext differentiates from other interfaces.
+	IsTypeAliasContext()
+}
+
+type TypeAliasContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	alias  antlr.Token
+	assign antlr.Token
+}
+
+func NewEmptyTypeAliasContext() *TypeAliasContext {
+	var p = new(TypeAliasContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_typeAlias
+	return p
+}
+
+func (*TypeAliasContext) IsTypeAliasContext() {}
+
+func NewTypeAliasContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeAliasContext {
+	var p = new(TypeAliasContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_typeAlias
+
+	return p
+}
+
+func (s *TypeAliasContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TypeAliasContext) GetAlias() antlr.Token { return s.alias }
+
+func (s *TypeAliasContext) GetAssign() antlr.Token { return s.assign }
+
+func (s *TypeAliasContext) SetAlias(v antlr.Token) { s.alias = v }
+
+func (s *TypeAliasContext) SetAssign(v antlr.Token) { s.assign = v }
+
+func (s *TypeAliasContext) DataType() IDataTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IDataTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IDataTypeContext)
+}
+
+func (s *TypeAliasContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *TypeAliasContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TypeAliasContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *TypeAliasContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitTypeAlias(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) TypeAlias() (localctx ITypeAliasContext) {
+	localctx = NewTypeAliasContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 30, ApiParserParserRULE_typeAlias)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	checkKeyword(p)
+	{
+		p.SetState(168)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*TypeAliasContext).alias = _m
+	}
+	p.SetState(170)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserT__0 {
+		{
+			p.SetState(169)
+
+			var _m = p.Match(ApiParserParserT__0)
+
+			localctx.(*TypeAliasContext).assign = _m
+		}
+
+	}
+	{
+		p.SetState(172)
+		p.DataType()
+	}
+
+	return localctx
+}
+
+// ITypeBlockStructContext is an interface to support dynamic dispatch.
+type ITypeBlockStructContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetStructName returns the structName token.
+	GetStructName() antlr.Token
+
+	// GetStructToken returns the structToken token.
+	GetStructToken() antlr.Token
+
+	// GetLbrace returns the lbrace token.
+	GetLbrace() antlr.Token
+
+	// GetRbrace returns the rbrace token.
+	GetRbrace() antlr.Token
+
+	// SetStructName sets the structName token.
+	SetStructName(antlr.Token)
+
+	// SetStructToken sets the structToken token.
+	SetStructToken(antlr.Token)
+
+	// SetLbrace sets the lbrace token.
+	SetLbrace(antlr.Token)
+
+	// SetRbrace sets the rbrace token.
+	SetRbrace(antlr.Token)
+
+	// IsTypeBlockStructContext differentiates from other interfaces.
+	IsTypeBlockStructContext()
+}
+
+type TypeBlockStructContext struct {
+	*antlr.BaseParserRuleContext
+	parser      antlr.Parser
+	structName  antlr.Token
+	structToken antlr.Token
+	lbrace      antlr.Token
+	rbrace      antlr.Token
+}
+
+func NewEmptyTypeBlockStructContext() *TypeBlockStructContext {
+	var p = new(TypeBlockStructContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_typeBlockStruct
+	return p
+}
+
+func (*TypeBlockStructContext) IsTypeBlockStructContext() {}
+
+func NewTypeBlockStructContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeBlockStructContext {
+	var p = new(TypeBlockStructContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_typeBlockStruct
+
+	return p
+}
+
+func (s *TypeBlockStructContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TypeBlockStructContext) GetStructName() antlr.Token { return s.structName }
+
+func (s *TypeBlockStructContext) GetStructToken() antlr.Token { return s.structToken }
+
+func (s *TypeBlockStructContext) GetLbrace() antlr.Token { return s.lbrace }
+
+func (s *TypeBlockStructContext) GetRbrace() antlr.Token { return s.rbrace }
+
+func (s *TypeBlockStructContext) SetStructName(v antlr.Token) { s.structName = v }
+
+func (s *TypeBlockStructContext) SetStructToken(v antlr.Token) { s.structToken = v }
+
+func (s *TypeBlockStructContext) SetLbrace(v antlr.Token) { s.lbrace = v }
+
+func (s *TypeBlockStructContext) SetRbrace(v antlr.Token) { s.rbrace = v }
+
+func (s *TypeBlockStructContext) AllID() []antlr.TerminalNode {
+	return s.GetTokens(ApiParserParserID)
+}
+
+func (s *TypeBlockStructContext) ID(i int) antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, i)
+}
+
+func (s *TypeBlockStructContext) AllField() []IFieldContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*IFieldContext)(nil)).Elem())
+	var tst = make([]IFieldContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(IFieldContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *TypeBlockStructContext) Field(i int) IFieldContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IFieldContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IFieldContext)
+}
+
+func (s *TypeBlockStructContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TypeBlockStructContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *TypeBlockStructContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitTypeBlockStruct(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) TypeBlockStruct() (localctx ITypeBlockStructContext) {
+	localctx = NewTypeBlockStructContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 32, ApiParserParserRULE_typeBlockStruct)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	var _alt int
+
+	p.EnterOuterAlt(localctx, 1)
+	checkKeyword(p)
+	{
+		p.SetState(175)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*TypeBlockStructContext).structName = _m
+	}
+	p.SetState(177)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserID {
+		{
+			p.SetState(176)
+
+			var _m = p.Match(ApiParserParserID)
+
+			localctx.(*TypeBlockStructContext).structToken = _m
+		}
+
+	}
+	{
+		p.SetState(179)
+
+		var _m = p.Match(ApiParserParserT__3)
+
+		localctx.(*TypeBlockStructContext).lbrace = _m
+	}
+	p.SetState(183)
+	p.GetErrorHandler().Sync(p)
+	_alt = p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 13, p.GetParserRuleContext())
+
+	for _alt != 2 && _alt != antlr.ATNInvalidAltNumber {
+		if _alt == 1 {
+			{
+				p.SetState(180)
+				p.Field()
+			}
+
+		}
+		p.SetState(185)
+		p.GetErrorHandler().Sync(p)
+		_alt = p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 13, p.GetParserRuleContext())
+	}
+	{
+		p.SetState(186)
+
+		var _m = p.Match(ApiParserParserT__4)
+
+		localctx.(*TypeBlockStructContext).rbrace = _m
+	}
+
+	return localctx
+}
+
+// ITypeBlockAliasContext is an interface to support dynamic dispatch.
+type ITypeBlockAliasContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetAlias returns the alias token.
+	GetAlias() antlr.Token
+
+	// GetAssign returns the assign token.
+	GetAssign() antlr.Token
+
+	// SetAlias sets the alias token.
+	SetAlias(antlr.Token)
+
+	// SetAssign sets the assign token.
+	SetAssign(antlr.Token)
+
+	// IsTypeBlockAliasContext differentiates from other interfaces.
+	IsTypeBlockAliasContext()
+}
+
+type TypeBlockAliasContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	alias  antlr.Token
+	assign antlr.Token
+}
+
+func NewEmptyTypeBlockAliasContext() *TypeBlockAliasContext {
+	var p = new(TypeBlockAliasContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_typeBlockAlias
+	return p
+}
+
+func (*TypeBlockAliasContext) IsTypeBlockAliasContext() {}
+
+func NewTypeBlockAliasContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TypeBlockAliasContext {
+	var p = new(TypeBlockAliasContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_typeBlockAlias
+
+	return p
+}
+
+func (s *TypeBlockAliasContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TypeBlockAliasContext) GetAlias() antlr.Token { return s.alias }
+
+func (s *TypeBlockAliasContext) GetAssign() antlr.Token { return s.assign }
+
+func (s *TypeBlockAliasContext) SetAlias(v antlr.Token) { s.alias = v }
+
+func (s *TypeBlockAliasContext) SetAssign(v antlr.Token) { s.assign = v }
+
+func (s *TypeBlockAliasContext) DataType() IDataTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IDataTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IDataTypeContext)
+}
+
+func (s *TypeBlockAliasContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *TypeBlockAliasContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TypeBlockAliasContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *TypeBlockAliasContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitTypeBlockAlias(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) TypeBlockAlias() (localctx ITypeBlockAliasContext) {
+	localctx = NewTypeBlockAliasContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 34, ApiParserParserRULE_typeBlockAlias)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	checkKeyword(p)
+	{
+		p.SetState(189)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*TypeBlockAliasContext).alias = _m
+	}
+	p.SetState(191)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserT__0 {
+		{
+			p.SetState(190)
+
+			var _m = p.Match(ApiParserParserT__0)
+
+			localctx.(*TypeBlockAliasContext).assign = _m
+		}
+
+	}
+	{
+		p.SetState(193)
+		p.DataType()
+	}
+
+	return localctx
+}
+
+// IFieldContext is an interface to support dynamic dispatch.
+type IFieldContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsFieldContext differentiates from other interfaces.
+	IsFieldContext()
+}
+
+type FieldContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyFieldContext() *FieldContext {
+	var p = new(FieldContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_field
+	return p
+}
+
+func (*FieldContext) IsFieldContext() {}
+
+func NewFieldContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FieldContext {
+	var p = new(FieldContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_field
+
+	return p
+}
+
+func (s *FieldContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *FieldContext) NormalField() INormalFieldContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*INormalFieldContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(INormalFieldContext)
+}
+
+func (s *FieldContext) AnonymousFiled() IAnonymousFiledContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IAnonymousFiledContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IAnonymousFiledContext)
+}
+
+func (s *FieldContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *FieldContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *FieldContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitField(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) Field() (localctx IFieldContext) {
+	localctx = NewFieldContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 36, ApiParserParserRULE_field)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(198)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 15, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		p.SetState(195)
+
+		if !(isNormal(p)) {
+			panic(antlr.NewFailedPredicateException(p, "isNormal(p)", ""))
+		}
+		{
+			p.SetState(196)
+			p.NormalField()
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(197)
+			p.AnonymousFiled()
+		}
+
+	}
+
+	return localctx
+}
+
+// INormalFieldContext is an interface to support dynamic dispatch.
+type INormalFieldContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetFieldName returns the fieldName token.
+	GetFieldName() antlr.Token
+
+	// GetTag returns the tag token.
+	GetTag() antlr.Token
+
+	// SetFieldName sets the fieldName token.
+	SetFieldName(antlr.Token)
+
+	// SetTag sets the tag token.
+	SetTag(antlr.Token)
+
+	// IsNormalFieldContext differentiates from other interfaces.
+	IsNormalFieldContext()
+}
+
+type NormalFieldContext struct {
+	*antlr.BaseParserRuleContext
+	parser    antlr.Parser
+	fieldName antlr.Token
+	tag       antlr.Token
+}
+
+func NewEmptyNormalFieldContext() *NormalFieldContext {
+	var p = new(NormalFieldContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_normalField
+	return p
+}
+
+func (*NormalFieldContext) IsNormalFieldContext() {}
+
+func NewNormalFieldContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *NormalFieldContext {
+	var p = new(NormalFieldContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_normalField
+
+	return p
+}
+
+func (s *NormalFieldContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *NormalFieldContext) GetFieldName() antlr.Token { return s.fieldName }
+
+func (s *NormalFieldContext) GetTag() antlr.Token { return s.tag }
+
+func (s *NormalFieldContext) SetFieldName(v antlr.Token) { s.fieldName = v }
+
+func (s *NormalFieldContext) SetTag(v antlr.Token) { s.tag = v }
+
+func (s *NormalFieldContext) DataType() IDataTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IDataTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IDataTypeContext)
+}
+
+func (s *NormalFieldContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *NormalFieldContext) RAW_STRING() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserRAW_STRING, 0)
+}
+
+func (s *NormalFieldContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *NormalFieldContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *NormalFieldContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitNormalField(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) NormalField() (localctx INormalFieldContext) {
+	localctx = NewNormalFieldContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 38, ApiParserParserRULE_normalField)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	checkKeyword(p)
+	{
+		p.SetState(201)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*NormalFieldContext).fieldName = _m
+	}
+	{
+		p.SetState(202)
+		p.DataType()
+	}
+	p.SetState(204)
+	p.GetErrorHandler().Sync(p)
+
+	if p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 16, p.GetParserRuleContext()) == 1 {
+		{
+			p.SetState(203)
+
+			var _m = p.Match(ApiParserParserRAW_STRING)
+
+			localctx.(*NormalFieldContext).tag = _m
+		}
+
+	}
+
+	return localctx
+}
+
+// IAnonymousFiledContext is an interface to support dynamic dispatch.
+type IAnonymousFiledContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetStar returns the star token.
+	GetStar() antlr.Token
+
+	// SetStar sets the star token.
+	SetStar(antlr.Token)
+
+	// IsAnonymousFiledContext differentiates from other interfaces.
+	IsAnonymousFiledContext()
+}
+
+type AnonymousFiledContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	star   antlr.Token
+}
+
+func NewEmptyAnonymousFiledContext() *AnonymousFiledContext {
+	var p = new(AnonymousFiledContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_anonymousFiled
+	return p
+}
+
+func (*AnonymousFiledContext) IsAnonymousFiledContext() {}
+
+func NewAnonymousFiledContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AnonymousFiledContext {
+	var p = new(AnonymousFiledContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_anonymousFiled
+
+	return p
+}
+
+func (s *AnonymousFiledContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *AnonymousFiledContext) GetStar() antlr.Token { return s.star }
+
+func (s *AnonymousFiledContext) SetStar(v antlr.Token) { s.star = v }
+
+func (s *AnonymousFiledContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *AnonymousFiledContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *AnonymousFiledContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *AnonymousFiledContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitAnonymousFiled(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) AnonymousFiled() (localctx IAnonymousFiledContext) {
+	localctx = NewAnonymousFiledContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 40, ApiParserParserRULE_anonymousFiled)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	p.SetState(207)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserT__5 {
+		{
+			p.SetState(206)
+
+			var _m = p.Match(ApiParserParserT__5)
+
+			localctx.(*AnonymousFiledContext).star = _m
+		}
+
+	}
+	{
+		p.SetState(209)
+		p.Match(ApiParserParserID)
+	}
+
+	return localctx
+}
+
+// IDataTypeContext is an interface to support dynamic dispatch.
+type IDataTypeContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetInter returns the inter token.
+	GetInter() antlr.Token
+
+	// GetTime returns the time token.
+	GetTime() antlr.Token
+
+	// SetInter sets the inter token.
+	SetInter(antlr.Token)
+
+	// SetTime sets the time token.
+	SetTime(antlr.Token)
+
+	// IsDataTypeContext differentiates from other interfaces.
+	IsDataTypeContext()
+}
+
+type DataTypeContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	inter  antlr.Token
+	time   antlr.Token
+}
+
+func NewEmptyDataTypeContext() *DataTypeContext {
+	var p = new(DataTypeContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_dataType
+	return p
+}
+
+func (*DataTypeContext) IsDataTypeContext() {}
+
+func NewDataTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DataTypeContext {
+	var p = new(DataTypeContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_dataType
+
+	return p
+}
+
+func (s *DataTypeContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *DataTypeContext) GetInter() antlr.Token { return s.inter }
+
+func (s *DataTypeContext) GetTime() antlr.Token { return s.time }
+
+func (s *DataTypeContext) SetInter(v antlr.Token) { s.inter = v }
+
+func (s *DataTypeContext) SetTime(v antlr.Token) { s.time = v }
+
+func (s *DataTypeContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *DataTypeContext) MapType() IMapTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IMapTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IMapTypeContext)
+}
+
+func (s *DataTypeContext) ArrayType() IArrayTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IArrayTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IArrayTypeContext)
+}
+
+func (s *DataTypeContext) INTERFACE() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserINTERFACE, 0)
+}
+
+func (s *DataTypeContext) PointerType() IPointerTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IPointerTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IPointerTypeContext)
+}
+
+func (s *DataTypeContext) TypeStruct() ITypeStructContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITypeStructContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITypeStructContext)
+}
+
+func (s *DataTypeContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *DataTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *DataTypeContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitDataType(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) DataType() (localctx IDataTypeContext) {
+	localctx = NewDataTypeContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 42, ApiParserParserRULE_dataType)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(219)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 18, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		isInterface(p)
+		{
+			p.SetState(212)
+			p.Match(ApiParserParserID)
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(213)
+			p.MapType()
+		}
+
+	case 3:
+		p.EnterOuterAlt(localctx, 3)
+		{
+			p.SetState(214)
+			p.ArrayType()
+		}
+
+	case 4:
+		p.EnterOuterAlt(localctx, 4)
+		{
+			p.SetState(215)
+
+			var _m = p.Match(ApiParserParserINTERFACE)
+
+			localctx.(*DataTypeContext).inter = _m
+		}
+
+	case 5:
+		p.EnterOuterAlt(localctx, 5)
+		{
+			p.SetState(216)
+
+			var _m = p.Match(ApiParserParserT__6)
+
+			localctx.(*DataTypeContext).time = _m
+		}
+
+	case 6:
+		p.EnterOuterAlt(localctx, 6)
+		{
+			p.SetState(217)
+			p.PointerType()
+		}
+
+	case 7:
+		p.EnterOuterAlt(localctx, 7)
+		{
+			p.SetState(218)
+			p.TypeStruct()
+		}
+
+	}
+
+	return localctx
+}
+
+// IPointerTypeContext is an interface to support dynamic dispatch.
+type IPointerTypeContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetStar returns the star token.
+	GetStar() antlr.Token
+
+	// SetStar sets the star token.
+	SetStar(antlr.Token)
+
+	// IsPointerTypeContext differentiates from other interfaces.
+	IsPointerTypeContext()
+}
+
+type PointerTypeContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	star   antlr.Token
+}
+
+func NewEmptyPointerTypeContext() *PointerTypeContext {
+	var p = new(PointerTypeContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_pointerType
+	return p
+}
+
+func (*PointerTypeContext) IsPointerTypeContext() {}
+
+func NewPointerTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *PointerTypeContext {
+	var p = new(PointerTypeContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_pointerType
+
+	return p
+}
+
+func (s *PointerTypeContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *PointerTypeContext) GetStar() antlr.Token { return s.star }
+
+func (s *PointerTypeContext) SetStar(v antlr.Token) { s.star = v }
+
+func (s *PointerTypeContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *PointerTypeContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *PointerTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *PointerTypeContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitPointerType(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) PointerType() (localctx IPointerTypeContext) {
+	localctx = NewPointerTypeContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 44, ApiParserParserRULE_pointerType)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(221)
+
+		var _m = p.Match(ApiParserParserT__5)
+
+		localctx.(*PointerTypeContext).star = _m
+	}
+	checkKeyword(p)
+	{
+		p.SetState(223)
+		p.Match(ApiParserParserID)
+	}
+
+	return localctx
+}
+
+// IMapTypeContext is an interface to support dynamic dispatch.
+type IMapTypeContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetMapToken returns the mapToken token.
+	GetMapToken() antlr.Token
+
+	// GetLbrack returns the lbrack token.
+	GetLbrack() antlr.Token
+
+	// GetKey returns the key token.
+	GetKey() antlr.Token
+
+	// GetRbrack returns the rbrack token.
+	GetRbrack() antlr.Token
+
+	// SetMapToken sets the mapToken token.
+	SetMapToken(antlr.Token)
+
+	// SetLbrack sets the lbrack token.
+	SetLbrack(antlr.Token)
+
+	// SetKey sets the key token.
+	SetKey(antlr.Token)
+
+	// SetRbrack sets the rbrack token.
+	SetRbrack(antlr.Token)
+
+	// GetValue returns the value rule contexts.
+	GetValue() IDataTypeContext
+
+	// SetValue sets the value rule contexts.
+	SetValue(IDataTypeContext)
+
+	// IsMapTypeContext differentiates from other interfaces.
+	IsMapTypeContext()
+}
+
+type MapTypeContext struct {
+	*antlr.BaseParserRuleContext
+	parser   antlr.Parser
+	mapToken antlr.Token
+	lbrack   antlr.Token
+	key      antlr.Token
+	rbrack   antlr.Token
+	value    IDataTypeContext
+}
+
+func NewEmptyMapTypeContext() *MapTypeContext {
+	var p = new(MapTypeContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_mapType
+	return p
+}
+
+func (*MapTypeContext) IsMapTypeContext() {}
+
+func NewMapTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MapTypeContext {
+	var p = new(MapTypeContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_mapType
+
+	return p
+}
+
+func (s *MapTypeContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *MapTypeContext) GetMapToken() antlr.Token { return s.mapToken }
+
+func (s *MapTypeContext) GetLbrack() antlr.Token { return s.lbrack }
+
+func (s *MapTypeContext) GetKey() antlr.Token { return s.key }
+
+func (s *MapTypeContext) GetRbrack() antlr.Token { return s.rbrack }
+
+func (s *MapTypeContext) SetMapToken(v antlr.Token) { s.mapToken = v }
+
+func (s *MapTypeContext) SetLbrack(v antlr.Token) { s.lbrack = v }
+
+func (s *MapTypeContext) SetKey(v antlr.Token) { s.key = v }
+
+func (s *MapTypeContext) SetRbrack(v antlr.Token) { s.rbrack = v }
+
+func (s *MapTypeContext) GetValue() IDataTypeContext { return s.value }
+
+func (s *MapTypeContext) SetValue(v IDataTypeContext) { s.value = v }
+
+func (s *MapTypeContext) AllID() []antlr.TerminalNode {
+	return s.GetTokens(ApiParserParserID)
+}
+
+func (s *MapTypeContext) ID(i int) antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, i)
+}
+
+func (s *MapTypeContext) DataType() IDataTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IDataTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IDataTypeContext)
+}
+
+func (s *MapTypeContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *MapTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *MapTypeContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitMapType(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) MapType() (localctx IMapTypeContext) {
+	localctx = NewMapTypeContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 46, ApiParserParserRULE_mapType)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	match(p, "map")
+	{
+		p.SetState(226)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*MapTypeContext).mapToken = _m
+	}
+	{
+		p.SetState(227)
+
+		var _m = p.Match(ApiParserParserT__7)
+
+		localctx.(*MapTypeContext).lbrack = _m
+	}
+	checkKey(p)
+	{
+		p.SetState(229)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*MapTypeContext).key = _m
+	}
+	{
+		p.SetState(230)
+
+		var _m = p.Match(ApiParserParserT__8)
+
+		localctx.(*MapTypeContext).rbrack = _m
+	}
+	{
+		p.SetState(231)
+
+		var _x = p.DataType()
+
+		localctx.(*MapTypeContext).value = _x
+	}
+
+	return localctx
+}
+
+// IArrayTypeContext is an interface to support dynamic dispatch.
+type IArrayTypeContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetLbrack returns the lbrack token.
+	GetLbrack() antlr.Token
+
+	// GetRbrack returns the rbrack token.
+	GetRbrack() antlr.Token
+
+	// SetLbrack sets the lbrack token.
+	SetLbrack(antlr.Token)
+
+	// SetRbrack sets the rbrack token.
+	SetRbrack(antlr.Token)
+
+	// IsArrayTypeContext differentiates from other interfaces.
+	IsArrayTypeContext()
+}
+
+type ArrayTypeContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	lbrack antlr.Token
+	rbrack antlr.Token
+}
+
+func NewEmptyArrayTypeContext() *ArrayTypeContext {
+	var p = new(ArrayTypeContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_arrayType
+	return p
+}
+
+func (*ArrayTypeContext) IsArrayTypeContext() {}
+
+func NewArrayTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ArrayTypeContext {
+	var p = new(ArrayTypeContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_arrayType
+
+	return p
+}
+
+func (s *ArrayTypeContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ArrayTypeContext) GetLbrack() antlr.Token { return s.lbrack }
+
+func (s *ArrayTypeContext) GetRbrack() antlr.Token { return s.rbrack }
+
+func (s *ArrayTypeContext) SetLbrack(v antlr.Token) { s.lbrack = v }
+
+func (s *ArrayTypeContext) SetRbrack(v antlr.Token) { s.rbrack = v }
+
+func (s *ArrayTypeContext) DataType() IDataTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IDataTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IDataTypeContext)
+}
+
+func (s *ArrayTypeContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ArrayTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ArrayTypeContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitArrayType(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ArrayType() (localctx IArrayTypeContext) {
+	localctx = NewArrayTypeContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 48, ApiParserParserRULE_arrayType)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(233)
+
+		var _m = p.Match(ApiParserParserT__7)
+
+		localctx.(*ArrayTypeContext).lbrack = _m
+	}
+	{
+		p.SetState(234)
+
+		var _m = p.Match(ApiParserParserT__8)
+
+		localctx.(*ArrayTypeContext).rbrack = _m
+	}
+	{
+		p.SetState(235)
+		p.DataType()
+	}
+
+	return localctx
+}
+
+// IServiceSpecContext is an interface to support dynamic dispatch.
+type IServiceSpecContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsServiceSpecContext differentiates from other interfaces.
+	IsServiceSpecContext()
+}
+
+type ServiceSpecContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyServiceSpecContext() *ServiceSpecContext {
+	var p = new(ServiceSpecContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_serviceSpec
+	return p
+}
+
+func (*ServiceSpecContext) IsServiceSpecContext() {}
+
+func NewServiceSpecContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ServiceSpecContext {
+	var p = new(ServiceSpecContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_serviceSpec
+
+	return p
+}
+
+func (s *ServiceSpecContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ServiceSpecContext) ServiceApi() IServiceApiContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IServiceApiContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IServiceApiContext)
+}
+
+func (s *ServiceSpecContext) AtServer() IAtServerContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IAtServerContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IAtServerContext)
+}
+
+func (s *ServiceSpecContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ServiceSpecContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ServiceSpecContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitServiceSpec(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ServiceSpec() (localctx IServiceSpecContext) {
+	localctx = NewServiceSpecContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 50, ApiParserParserRULE_serviceSpec)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	p.SetState(238)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserATSERVER {
+		{
+			p.SetState(237)
+			p.AtServer()
+		}
+
+	}
+	{
+		p.SetState(240)
+		p.ServiceApi()
+	}
+
+	return localctx
+}
+
+// IAtServerContext is an interface to support dynamic dispatch.
+type IAtServerContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetLp returns the lp token.
+	GetLp() antlr.Token
+
+	// GetRp returns the rp token.
+	GetRp() antlr.Token
+
+	// SetLp sets the lp token.
+	SetLp(antlr.Token)
+
+	// SetRp sets the rp token.
+	SetRp(antlr.Token)
+
+	// IsAtServerContext differentiates from other interfaces.
+	IsAtServerContext()
+}
+
+type AtServerContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	lp     antlr.Token
+	rp     antlr.Token
+}
+
+func NewEmptyAtServerContext() *AtServerContext {
+	var p = new(AtServerContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_atServer
+	return p
+}
+
+func (*AtServerContext) IsAtServerContext() {}
+
+func NewAtServerContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AtServerContext {
+	var p = new(AtServerContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_atServer
+
+	return p
+}
+
+func (s *AtServerContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *AtServerContext) GetLp() antlr.Token { return s.lp }
+
+func (s *AtServerContext) GetRp() antlr.Token { return s.rp }
+
+func (s *AtServerContext) SetLp(v antlr.Token) { s.lp = v }
+
+func (s *AtServerContext) SetRp(v antlr.Token) { s.rp = v }
+
+func (s *AtServerContext) ATSERVER() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserATSERVER, 0)
+}
+
+func (s *AtServerContext) AllKvLit() []IKvLitContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*IKvLitContext)(nil)).Elem())
+	var tst = make([]IKvLitContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(IKvLitContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *AtServerContext) KvLit(i int) IKvLitContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IKvLitContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IKvLitContext)
+}
+
+func (s *AtServerContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *AtServerContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *AtServerContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitAtServer(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) AtServer() (localctx IAtServerContext) {
+	localctx = NewAtServerContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 52, ApiParserParserRULE_atServer)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(242)
+		p.Match(ApiParserParserATSERVER)
+	}
+	{
+		p.SetState(243)
+
+		var _m = p.Match(ApiParserParserT__1)
+
+		localctx.(*AtServerContext).lp = _m
+	}
+	p.SetState(245)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	for ok := true; ok; ok = _la == ApiParserParserID {
+		{
+			p.SetState(244)
+			p.KvLit()
+		}
+
+		p.SetState(247)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+	}
+	{
+		p.SetState(249)
+
+		var _m = p.Match(ApiParserParserT__2)
+
+		localctx.(*AtServerContext).rp = _m
+	}
+
+	return localctx
+}
+
+// IServiceApiContext is an interface to support dynamic dispatch.
+type IServiceApiContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetServiceToken returns the serviceToken token.
+	GetServiceToken() antlr.Token
+
+	// GetLbrace returns the lbrace token.
+	GetLbrace() antlr.Token
+
+	// GetRbrace returns the rbrace token.
+	GetRbrace() antlr.Token
+
+	// SetServiceToken sets the serviceToken token.
+	SetServiceToken(antlr.Token)
+
+	// SetLbrace sets the lbrace token.
+	SetLbrace(antlr.Token)
+
+	// SetRbrace sets the rbrace token.
+	SetRbrace(antlr.Token)
+
+	// IsServiceApiContext differentiates from other interfaces.
+	IsServiceApiContext()
+}
+
+type ServiceApiContext struct {
+	*antlr.BaseParserRuleContext
+	parser       antlr.Parser
+	serviceToken antlr.Token
+	lbrace       antlr.Token
+	rbrace       antlr.Token
+}
+
+func NewEmptyServiceApiContext() *ServiceApiContext {
+	var p = new(ServiceApiContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_serviceApi
+	return p
+}
+
+func (*ServiceApiContext) IsServiceApiContext() {}
+
+func NewServiceApiContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ServiceApiContext {
+	var p = new(ServiceApiContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_serviceApi
+
+	return p
+}
+
+func (s *ServiceApiContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ServiceApiContext) GetServiceToken() antlr.Token { return s.serviceToken }
+
+func (s *ServiceApiContext) GetLbrace() antlr.Token { return s.lbrace }
+
+func (s *ServiceApiContext) GetRbrace() antlr.Token { return s.rbrace }
+
+func (s *ServiceApiContext) SetServiceToken(v antlr.Token) { s.serviceToken = v }
+
+func (s *ServiceApiContext) SetLbrace(v antlr.Token) { s.lbrace = v }
+
+func (s *ServiceApiContext) SetRbrace(v antlr.Token) { s.rbrace = v }
+
+func (s *ServiceApiContext) ServiceName() IServiceNameContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IServiceNameContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IServiceNameContext)
+}
+
+func (s *ServiceApiContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *ServiceApiContext) AllServiceRoute() []IServiceRouteContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*IServiceRouteContext)(nil)).Elem())
+	var tst = make([]IServiceRouteContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(IServiceRouteContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *ServiceApiContext) ServiceRoute(i int) IServiceRouteContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IServiceRouteContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IServiceRouteContext)
+}
+
+func (s *ServiceApiContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ServiceApiContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ServiceApiContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitServiceApi(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ServiceApi() (localctx IServiceApiContext) {
+	localctx = NewServiceApiContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 54, ApiParserParserRULE_serviceApi)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	match(p, "service")
+	{
+		p.SetState(252)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*ServiceApiContext).serviceToken = _m
+	}
+	{
+		p.SetState(253)
+		p.ServiceName()
+	}
+	{
+		p.SetState(254)
+
+		var _m = p.Match(ApiParserParserT__3)
+
+		localctx.(*ServiceApiContext).lbrace = _m
+	}
+	p.SetState(258)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	for ((_la)&-(0x1f+1)) == 0 && ((1<<uint(_la))&((1<<ApiParserParserATDOC)|(1<<ApiParserParserATHANDLER)|(1<<ApiParserParserATSERVER))) != 0 {
+		{
+			p.SetState(255)
+			p.ServiceRoute()
+		}
+
+		p.SetState(260)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+	}
+	{
+		p.SetState(261)
+
+		var _m = p.Match(ApiParserParserT__4)
+
+		localctx.(*ServiceApiContext).rbrace = _m
+	}
+
+	return localctx
+}
+
+// IServiceRouteContext is an interface to support dynamic dispatch.
+type IServiceRouteContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsServiceRouteContext differentiates from other interfaces.
+	IsServiceRouteContext()
+}
+
+type ServiceRouteContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyServiceRouteContext() *ServiceRouteContext {
+	var p = new(ServiceRouteContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_serviceRoute
+	return p
+}
+
+func (*ServiceRouteContext) IsServiceRouteContext() {}
+
+func NewServiceRouteContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ServiceRouteContext {
+	var p = new(ServiceRouteContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_serviceRoute
+
+	return p
+}
+
+func (s *ServiceRouteContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ServiceRouteContext) Route() IRouteContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IRouteContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IRouteContext)
+}
+
+func (s *ServiceRouteContext) AtServer() IAtServerContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IAtServerContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IAtServerContext)
+}
+
+func (s *ServiceRouteContext) AtHandler() IAtHandlerContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IAtHandlerContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IAtHandlerContext)
+}
+
+func (s *ServiceRouteContext) AtDoc() IAtDocContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IAtDocContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IAtDocContext)
+}
+
+func (s *ServiceRouteContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ServiceRouteContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ServiceRouteContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitServiceRoute(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ServiceRoute() (localctx IServiceRouteContext) {
+	localctx = NewServiceRouteContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 56, ApiParserParserRULE_serviceRoute)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	p.SetState(264)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserATDOC {
+		{
+			p.SetState(263)
+			p.AtDoc()
+		}
+
+	}
+	p.SetState(268)
+	p.GetErrorHandler().Sync(p)
+
+	switch p.GetTokenStream().LA(1) {
+	case ApiParserParserATSERVER:
+		{
+			p.SetState(266)
+			p.AtServer()
+		}
+
+	case ApiParserParserATHANDLER:
+		{
+			p.SetState(267)
+			p.AtHandler()
+		}
+
+	default:
+		panic(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil))
+	}
+	{
+		p.SetState(270)
+		p.Route()
+	}
+
+	return localctx
+}
+
+// IAtDocContext is an interface to support dynamic dispatch.
+type IAtDocContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetLp returns the lp token.
+	GetLp() antlr.Token
+
+	// GetRp returns the rp token.
+	GetRp() antlr.Token
+
+	// SetLp sets the lp token.
+	SetLp(antlr.Token)
+
+	// SetRp sets the rp token.
+	SetRp(antlr.Token)
+
+	// IsAtDocContext differentiates from other interfaces.
+	IsAtDocContext()
+}
+
+type AtDocContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	lp     antlr.Token
+	rp     antlr.Token
+}
+
+func NewEmptyAtDocContext() *AtDocContext {
+	var p = new(AtDocContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_atDoc
+	return p
+}
+
+func (*AtDocContext) IsAtDocContext() {}
+
+func NewAtDocContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AtDocContext {
+	var p = new(AtDocContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_atDoc
+
+	return p
+}
+
+func (s *AtDocContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *AtDocContext) GetLp() antlr.Token { return s.lp }
+
+func (s *AtDocContext) GetRp() antlr.Token { return s.rp }
+
+func (s *AtDocContext) SetLp(v antlr.Token) { s.lp = v }
+
+func (s *AtDocContext) SetRp(v antlr.Token) { s.rp = v }
+
+func (s *AtDocContext) ATDOC() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserATDOC, 0)
+}
+
+func (s *AtDocContext) STRING() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserSTRING, 0)
+}
+
+func (s *AtDocContext) AllKvLit() []IKvLitContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*IKvLitContext)(nil)).Elem())
+	var tst = make([]IKvLitContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(IKvLitContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *AtDocContext) KvLit(i int) IKvLitContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IKvLitContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IKvLitContext)
+}
+
+func (s *AtDocContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *AtDocContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *AtDocContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitAtDoc(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) AtDoc() (localctx IAtDocContext) {
+	localctx = NewAtDocContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 58, ApiParserParserRULE_atDoc)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(272)
+		p.Match(ApiParserParserATDOC)
+	}
+	p.SetState(274)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserT__1 {
+		{
+			p.SetState(273)
+
+			var _m = p.Match(ApiParserParserT__1)
+
+			localctx.(*AtDocContext).lp = _m
+		}
+
+	}
+	p.SetState(282)
+	p.GetErrorHandler().Sync(p)
+
+	switch p.GetTokenStream().LA(1) {
+	case ApiParserParserID:
+		p.SetState(277)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+
+		for ok := true; ok; ok = _la == ApiParserParserID {
+			{
+				p.SetState(276)
+				p.KvLit()
+			}
+
+			p.SetState(279)
+			p.GetErrorHandler().Sync(p)
+			_la = p.GetTokenStream().LA(1)
+		}
+
+	case ApiParserParserSTRING:
+		{
+			p.SetState(281)
+			p.Match(ApiParserParserSTRING)
+		}
+
+	default:
+		panic(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil))
+	}
+	p.SetState(285)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserT__2 {
+		{
+			p.SetState(284)
+
+			var _m = p.Match(ApiParserParserT__2)
+
+			localctx.(*AtDocContext).rp = _m
+		}
+
+	}
+
+	return localctx
+}
+
+// IAtHandlerContext is an interface to support dynamic dispatch.
+type IAtHandlerContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsAtHandlerContext differentiates from other interfaces.
+	IsAtHandlerContext()
+}
+
+type AtHandlerContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyAtHandlerContext() *AtHandlerContext {
+	var p = new(AtHandlerContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_atHandler
+	return p
+}
+
+func (*AtHandlerContext) IsAtHandlerContext() {}
+
+func NewAtHandlerContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AtHandlerContext {
+	var p = new(AtHandlerContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_atHandler
+
+	return p
+}
+
+func (s *AtHandlerContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *AtHandlerContext) ATHANDLER() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserATHANDLER, 0)
+}
+
+func (s *AtHandlerContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *AtHandlerContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *AtHandlerContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *AtHandlerContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitAtHandler(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) AtHandler() (localctx IAtHandlerContext) {
+	localctx = NewAtHandlerContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 60, ApiParserParserRULE_atHandler)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(287)
+		p.Match(ApiParserParserATHANDLER)
+	}
+	{
+		p.SetState(288)
+		p.Match(ApiParserParserID)
+	}
+
+	return localctx
+}
+
+// IRouteContext is an interface to support dynamic dispatch.
+type IRouteContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetHttpMethod returns the httpMethod token.
+	GetHttpMethod() antlr.Token
+
+	// GetReturnToken returns the returnToken token.
+	GetReturnToken() antlr.Token
+
+	// SetHttpMethod sets the httpMethod token.
+	SetHttpMethod(antlr.Token)
+
+	// SetReturnToken sets the returnToken token.
+	SetReturnToken(antlr.Token)
+
+	// GetRequest returns the request rule contexts.
+	GetRequest() IBodyContext
+
+	// GetResponse returns the response rule contexts.
+	GetResponse() IReplybodyContext
+
+	// SetRequest sets the request rule contexts.
+	SetRequest(IBodyContext)
+
+	// SetResponse sets the response rule contexts.
+	SetResponse(IReplybodyContext)
+
+	// IsRouteContext differentiates from other interfaces.
+	IsRouteContext()
+}
+
+type RouteContext struct {
+	*antlr.BaseParserRuleContext
+	parser      antlr.Parser
+	httpMethod  antlr.Token
+	request     IBodyContext
+	returnToken antlr.Token
+	response    IReplybodyContext
+}
+
+func NewEmptyRouteContext() *RouteContext {
+	var p = new(RouteContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_route
+	return p
+}
+
+func (*RouteContext) IsRouteContext() {}
+
+func NewRouteContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *RouteContext {
+	var p = new(RouteContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_route
+
+	return p
+}
+
+func (s *RouteContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *RouteContext) GetHttpMethod() antlr.Token { return s.httpMethod }
+
+func (s *RouteContext) GetReturnToken() antlr.Token { return s.returnToken }
+
+func (s *RouteContext) SetHttpMethod(v antlr.Token) { s.httpMethod = v }
+
+func (s *RouteContext) SetReturnToken(v antlr.Token) { s.returnToken = v }
+
+func (s *RouteContext) GetRequest() IBodyContext { return s.request }
+
+func (s *RouteContext) GetResponse() IReplybodyContext { return s.response }
+
+func (s *RouteContext) SetRequest(v IBodyContext) { s.request = v }
+
+func (s *RouteContext) SetResponse(v IReplybodyContext) { s.response = v }
+
+func (s *RouteContext) Path() IPathContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IPathContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IPathContext)
+}
+
+func (s *RouteContext) AllID() []antlr.TerminalNode {
+	return s.GetTokens(ApiParserParserID)
+}
+
+func (s *RouteContext) ID(i int) antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, i)
+}
+
+func (s *RouteContext) Body() IBodyContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IBodyContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IBodyContext)
+}
+
+func (s *RouteContext) Replybody() IReplybodyContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IReplybodyContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IReplybodyContext)
+}
+
+func (s *RouteContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *RouteContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *RouteContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitRoute(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) Route() (localctx IRouteContext) {
+	localctx = NewRouteContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 62, ApiParserParserRULE_route)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	checkHttpMethod(p)
+	{
+		p.SetState(291)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*RouteContext).httpMethod = _m
+	}
+	{
+		p.SetState(292)
+		p.Path()
+	}
+	p.SetState(294)
+	p.GetErrorHandler().Sync(p)
+
+	if p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 28, p.GetParserRuleContext()) == 1 {
+		{
+			p.SetState(293)
+
+			var _x = p.Body()
+
+			localctx.(*RouteContext).request = _x
+		}
+
+	}
+	p.SetState(297)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserID {
+		{
+			p.SetState(296)
+
+			var _m = p.Match(ApiParserParserID)
+
+			localctx.(*RouteContext).returnToken = _m
+		}
+
+	}
+	p.SetState(300)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserT__1 {
+		{
+			p.SetState(299)
+
+			var _x = p.Replybody()
+
+			localctx.(*RouteContext).response = _x
+		}
+
+	}
+
+	return localctx
+}
+
+// IBodyContext is an interface to support dynamic dispatch.
+type IBodyContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetLp returns the lp token.
+	GetLp() antlr.Token
+
+	// GetRp returns the rp token.
+	GetRp() antlr.Token
+
+	// SetLp sets the lp token.
+	SetLp(antlr.Token)
+
+	// SetRp sets the rp token.
+	SetRp(antlr.Token)
+
+	// IsBodyContext differentiates from other interfaces.
+	IsBodyContext()
+}
+
+type BodyContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	lp     antlr.Token
+	rp     antlr.Token
+}
+
+func NewEmptyBodyContext() *BodyContext {
+	var p = new(BodyContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_body
+	return p
+}
+
+func (*BodyContext) IsBodyContext() {}
+
+func NewBodyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *BodyContext {
+	var p = new(BodyContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_body
+
+	return p
+}
+
+func (s *BodyContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *BodyContext) GetLp() antlr.Token { return s.lp }
+
+func (s *BodyContext) GetRp() antlr.Token { return s.rp }
+
+func (s *BodyContext) SetLp(v antlr.Token) { s.lp = v }
+
+func (s *BodyContext) SetRp(v antlr.Token) { s.rp = v }
+
+func (s *BodyContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *BodyContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *BodyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *BodyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitBody(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) Body() (localctx IBodyContext) {
+	localctx = NewBodyContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 64, ApiParserParserRULE_body)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(302)
+
+		var _m = p.Match(ApiParserParserT__1)
+
+		localctx.(*BodyContext).lp = _m
+	}
+	p.SetState(304)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if _la == ApiParserParserID {
+		{
+			p.SetState(303)
+			p.Match(ApiParserParserID)
+		}
+
+	}
+	{
+		p.SetState(306)
+
+		var _m = p.Match(ApiParserParserT__2)
+
+		localctx.(*BodyContext).rp = _m
+	}
+
+	return localctx
+}
+
+// IReplybodyContext is an interface to support dynamic dispatch.
+type IReplybodyContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetLp returns the lp token.
+	GetLp() antlr.Token
+
+	// GetRp returns the rp token.
+	GetRp() antlr.Token
+
+	// SetLp sets the lp token.
+	SetLp(antlr.Token)
+
+	// SetRp sets the rp token.
+	SetRp(antlr.Token)
+
+	// IsReplybodyContext differentiates from other interfaces.
+	IsReplybodyContext()
+}
+
+type ReplybodyContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	lp     antlr.Token
+	rp     antlr.Token
+}
+
+func NewEmptyReplybodyContext() *ReplybodyContext {
+	var p = new(ReplybodyContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_replybody
+	return p
+}
+
+func (*ReplybodyContext) IsReplybodyContext() {}
+
+func NewReplybodyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ReplybodyContext {
+	var p = new(ReplybodyContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_replybody
+
+	return p
+}
+
+func (s *ReplybodyContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ReplybodyContext) GetLp() antlr.Token { return s.lp }
+
+func (s *ReplybodyContext) GetRp() antlr.Token { return s.rp }
+
+func (s *ReplybodyContext) SetLp(v antlr.Token) { s.lp = v }
+
+func (s *ReplybodyContext) SetRp(v antlr.Token) { s.rp = v }
+
+func (s *ReplybodyContext) DataType() IDataTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IDataTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IDataTypeContext)
+}
+
+func (s *ReplybodyContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ReplybodyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ReplybodyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitReplybody(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) Replybody() (localctx IReplybodyContext) {
+	localctx = NewReplybodyContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 66, ApiParserParserRULE_replybody)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(308)
+
+		var _m = p.Match(ApiParserParserT__1)
+
+		localctx.(*ReplybodyContext).lp = _m
+	}
+	p.SetState(310)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	if ((_la)&-(0x1f+1)) == 0 && ((1<<uint(_la))&((1<<ApiParserParserT__5)|(1<<ApiParserParserT__6)|(1<<ApiParserParserT__7)|(1<<ApiParserParserINTERFACE)|(1<<ApiParserParserID))) != 0 {
+		{
+			p.SetState(309)
+			p.DataType()
+		}
+
+	}
+	{
+		p.SetState(312)
+
+		var _m = p.Match(ApiParserParserT__2)
+
+		localctx.(*ReplybodyContext).rp = _m
+	}
+
+	return localctx
+}
+
+// IKvLitContext is an interface to support dynamic dispatch.
+type IKvLitContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// GetKey returns the key token.
+	GetKey() antlr.Token
+
+	// GetValue returns the value token.
+	GetValue() antlr.Token
+
+	// SetKey sets the key token.
+	SetKey(antlr.Token)
+
+	// SetValue sets the value token.
+	SetValue(antlr.Token)
+
+	// IsKvLitContext differentiates from other interfaces.
+	IsKvLitContext()
+}
+
+type KvLitContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+	key    antlr.Token
+	value  antlr.Token
+}
+
+func NewEmptyKvLitContext() *KvLitContext {
+	var p = new(KvLitContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_kvLit
+	return p
+}
+
+func (*KvLitContext) IsKvLitContext() {}
+
+func NewKvLitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *KvLitContext {
+	var p = new(KvLitContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_kvLit
+
+	return p
+}
+
+func (s *KvLitContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *KvLitContext) GetKey() antlr.Token { return s.key }
+
+func (s *KvLitContext) GetValue() antlr.Token { return s.value }
+
+func (s *KvLitContext) SetKey(v antlr.Token) { s.key = v }
+
+func (s *KvLitContext) SetValue(v antlr.Token) { s.value = v }
+
+func (s *KvLitContext) ID() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, 0)
+}
+
+func (s *KvLitContext) LINE_VALUE() antlr.TerminalNode {
+	return s.GetToken(ApiParserParserLINE_VALUE, 0)
+}
+
+func (s *KvLitContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *KvLitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *KvLitContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitKvLit(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) KvLit() (localctx IKvLitContext) {
+	localctx = NewKvLitContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 68, ApiParserParserRULE_kvLit)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(314)
+
+		var _m = p.Match(ApiParserParserID)
+
+		localctx.(*KvLitContext).key = _m
+	}
+	checkKeyValue(p)
+	{
+		p.SetState(316)
+
+		var _m = p.Match(ApiParserParserLINE_VALUE)
+
+		localctx.(*KvLitContext).value = _m
+	}
+
+	return localctx
+}
+
+// IServiceNameContext is an interface to support dynamic dispatch.
+type IServiceNameContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsServiceNameContext differentiates from other interfaces.
+	IsServiceNameContext()
+}
+
+type ServiceNameContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyServiceNameContext() *ServiceNameContext {
+	var p = new(ServiceNameContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_serviceName
+	return p
+}
+
+func (*ServiceNameContext) IsServiceNameContext() {}
+
+func NewServiceNameContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ServiceNameContext {
+	var p = new(ServiceNameContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_serviceName
+
+	return p
+}
+
+func (s *ServiceNameContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ServiceNameContext) AllID() []antlr.TerminalNode {
+	return s.GetTokens(ApiParserParserID)
+}
+
+func (s *ServiceNameContext) ID(i int) antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, i)
+}
+
+func (s *ServiceNameContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ServiceNameContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *ServiceNameContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitServiceName(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) ServiceName() (localctx IServiceNameContext) {
+	localctx = NewServiceNameContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 70, ApiParserParserRULE_serviceName)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	p.SetState(322)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	for ok := true; ok; ok = _la == ApiParserParserID {
+		{
+			p.SetState(318)
+			p.Match(ApiParserParserID)
+		}
+		p.SetState(320)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+
+		if _la == ApiParserParserT__9 {
+			{
+				p.SetState(319)
+				p.Match(ApiParserParserT__9)
+			}
+
+		}
+
+		p.SetState(324)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+	}
+
+	return localctx
+}
+
+// IPathContext is an interface to support dynamic dispatch.
+type IPathContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsPathContext differentiates from other interfaces.
+	IsPathContext()
+}
+
+type PathContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyPathContext() *PathContext {
+	var p = new(PathContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = ApiParserParserRULE_path
+	return p
+}
+
+func (*PathContext) IsPathContext() {}
+
+func NewPathContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *PathContext {
+	var p = new(PathContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = ApiParserParserRULE_path
+
+	return p
+}
+
+func (s *PathContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *PathContext) AllID() []antlr.TerminalNode {
+	return s.GetTokens(ApiParserParserID)
+}
+
+func (s *PathContext) ID(i int) antlr.TerminalNode {
+	return s.GetToken(ApiParserParserID, i)
+}
+
+func (s *PathContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *PathContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (s *PathContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
+	switch t := visitor.(type) {
+	case ApiParserVisitor:
+		return t.VisitPath(s)
+
+	default:
+		return t.VisitChildren(s)
+	}
+}
+
+func (p *ApiParserParser) Path() (localctx IPathContext) {
+	localctx = NewPathContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 72, ApiParserParserRULE_path)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	p.SetState(341)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	for ok := true; ok; ok = _la == ApiParserParserT__10 || _la == ApiParserParserT__11 {
+		p.SetState(341)
+		p.GetErrorHandler().Sync(p)
+
+		switch p.GetTokenStream().LA(1) {
+		case ApiParserParserT__10:
+			{
+				p.SetState(326)
+				p.Match(ApiParserParserT__10)
+			}
+
+			{
+				p.SetState(327)
+				p.Match(ApiParserParserID)
+			}
+			p.SetState(332)
+			p.GetErrorHandler().Sync(p)
+			_la = p.GetTokenStream().LA(1)
+
+			for _la == ApiParserParserT__9 {
+				{
+					p.SetState(328)
+					p.Match(ApiParserParserT__9)
+				}
+				{
+					p.SetState(329)
+					p.Match(ApiParserParserID)
+				}
+
+				p.SetState(334)
+				p.GetErrorHandler().Sync(p)
+				_la = p.GetTokenStream().LA(1)
+			}
+
+		case ApiParserParserT__11:
+			{
+				p.SetState(335)
+				p.Match(ApiParserParserT__11)
+			}
+
+			{
+				p.SetState(336)
+				p.Match(ApiParserParserID)
+			}
+			p.SetState(339)
+			p.GetErrorHandler().Sync(p)
+			_la = p.GetTokenStream().LA(1)
+
+			if _la == ApiParserParserT__9 {
+				{
+					p.SetState(337)
+					p.Match(ApiParserParserT__9)
+				}
+				{
+					p.SetState(338)
+					p.Match(ApiParserParserID)
+				}
+
+			}
+
+		default:
+			panic(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil))
+		}
+
+		p.SetState(343)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+	}
+
+	return localctx
+}
+
+func (p *ApiParserParser) Sempred(localctx antlr.RuleContext, ruleIndex, predIndex int) bool {
+	switch ruleIndex {
+	case 18:
+		var t *FieldContext = nil
+		if localctx != nil {
+			t = localctx.(*FieldContext)
+		}
+		return p.Field_Sempred(t, predIndex)
+
+	default:
+		panic("No predicate with index: " + fmt.Sprint(ruleIndex))
+	}
+}
+
+func (p *ApiParserParser) Field_Sempred(localctx antlr.RuleContext, predIndex int) bool {
+	switch predIndex {
+	case 0:
+		return isNormal(p)
+
+	default:
+		panic("No predicate with index: " + fmt.Sprint(predIndex))
+	}
+}

+ 120 - 0
tools/goctl/api/parser/g4/gen/api/apiparser_visitor.go

@@ -0,0 +1,120 @@
+// Code generated from tools/goctl/api/parser/g4/ApiParser.g4 by ANTLR 4.9. DO NOT EDIT.
+
+package api // ApiParser
+import "github.com/antlr/antlr4/runtime/Go/antlr"
+
+// A complete Visitor for a parse tree produced by ApiParserParser.
+type ApiParserVisitor interface {
+	antlr.ParseTreeVisitor
+
+	// Visit a parse tree produced by ApiParserParser#api.
+	VisitApi(ctx *ApiContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#spec.
+	VisitSpec(ctx *SpecContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#syntaxLit.
+	VisitSyntaxLit(ctx *SyntaxLitContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#importSpec.
+	VisitImportSpec(ctx *ImportSpecContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#importLit.
+	VisitImportLit(ctx *ImportLitContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#importBlock.
+	VisitImportBlock(ctx *ImportBlockContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#importBlockValue.
+	VisitImportBlockValue(ctx *ImportBlockValueContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#importValue.
+	VisitImportValue(ctx *ImportValueContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#infoSpec.
+	VisitInfoSpec(ctx *InfoSpecContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#typeSpec.
+	VisitTypeSpec(ctx *TypeSpecContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#typeLit.
+	VisitTypeLit(ctx *TypeLitContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#typeBlock.
+	VisitTypeBlock(ctx *TypeBlockContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#typeLitBody.
+	VisitTypeLitBody(ctx *TypeLitBodyContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#typeBlockBody.
+	VisitTypeBlockBody(ctx *TypeBlockBodyContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#typeStruct.
+	VisitTypeStruct(ctx *TypeStructContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#typeAlias.
+	VisitTypeAlias(ctx *TypeAliasContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#typeBlockStruct.
+	VisitTypeBlockStruct(ctx *TypeBlockStructContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#typeBlockAlias.
+	VisitTypeBlockAlias(ctx *TypeBlockAliasContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#field.
+	VisitField(ctx *FieldContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#normalField.
+	VisitNormalField(ctx *NormalFieldContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#anonymousFiled.
+	VisitAnonymousFiled(ctx *AnonymousFiledContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#dataType.
+	VisitDataType(ctx *DataTypeContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#pointerType.
+	VisitPointerType(ctx *PointerTypeContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#mapType.
+	VisitMapType(ctx *MapTypeContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#arrayType.
+	VisitArrayType(ctx *ArrayTypeContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#serviceSpec.
+	VisitServiceSpec(ctx *ServiceSpecContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#atServer.
+	VisitAtServer(ctx *AtServerContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#serviceApi.
+	VisitServiceApi(ctx *ServiceApiContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#serviceRoute.
+	VisitServiceRoute(ctx *ServiceRouteContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#atDoc.
+	VisitAtDoc(ctx *AtDocContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#atHandler.
+	VisitAtHandler(ctx *AtHandlerContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#route.
+	VisitRoute(ctx *RouteContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#body.
+	VisitBody(ctx *BodyContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#replybody.
+	VisitReplybody(ctx *ReplybodyContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#kvLit.
+	VisitKvLit(ctx *KvLitContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#serviceName.
+	VisitServiceName(ctx *ServiceNameContext) interface{}
+
+	// Visit a parse tree produced by ApiParserParser#path.
+	VisitPath(ctx *PathContext) interface{}
+}

+ 222 - 0
tools/goctl/api/parser/g4/gen/api/baseparser.go

@@ -0,0 +1,222 @@
+package api
+
+import (
+	"fmt"
+	"net/http"
+	"regexp"
+	"strings"
+	"unicode"
+
+	"github.com/antlr/antlr4/runtime/Go/antlr"
+)
+
+const (
+	versionRegex     = `(?m)"v[1-9][0-9]*"`
+	importValueRegex = `(?m)"(/?[a-zA-Z0-9_#-])+\.api"`
+	tagRegex         = `(?m)\x60[a-z]+:".+"\x60`
+)
+
+var holder = struct{}{}
+var kind = map[string]struct{}{
+	"bool":       holder,
+	"int":        holder,
+	"int8":       holder,
+	"int16":      holder,
+	"int32":      holder,
+	"int64":      holder,
+	"uint":       holder,
+	"uint8":      holder,
+	"uint16":     holder,
+	"uint32":     holder,
+	"uint64":     holder,
+	"uintptr":    holder,
+	"float32":    holder,
+	"float64":    holder,
+	"complex64":  holder,
+	"complex128": holder,
+	"string":     holder,
+	"byte":       holder,
+	"rune":       holder,
+}
+
+func match(p *ApiParserParser, text string) {
+	v := getCurrentTokenText(p)
+
+	if v != text {
+		notifyErrorListeners(p, expecting(text, v))
+	}
+}
+
+func checkVersion(p *ApiParserParser) {
+	v := getCurrentTokenText(p)
+	if !matchRegex(v, versionRegex) {
+		notifyErrorListeners(p, mismatched("version", v))
+	}
+}
+
+func checkImportValue(p *ApiParserParser) {
+	v := getCurrentTokenText(p)
+	if !matchRegex(v, importValueRegex) {
+		notifyErrorListeners(p, mismatched("import value", v))
+	}
+}
+
+func checkKeyValue(p *ApiParserParser) {
+	v := getCurrentTokenText(p)
+	if !strings.HasPrefix(v, ":") {
+		notifyErrorListeners(p, mismatched(":", v))
+	}
+
+	v = strings.TrimPrefix(v, ":")
+	v = strings.TrimFunc(v, func(r rune) bool {
+		return unicode.IsSpace(r)
+	})
+	setCurrentTokenText(p, v)
+}
+
+func checkHttpMethod(p *ApiParserParser) {
+	method := getCurrentTokenText(p)
+	uppler := strings.ToUpper(method)
+	switch uppler {
+	case http.MethodPost, http.MethodGet, http.MethodHead,
+		http.MethodPut, http.MethodPatch, http.MethodDelete,
+		http.MethodConnect, http.MethodOptions, http.MethodTrace:
+		if method != strings.ToLower(method) {
+			notifyErrorListeners(p, expecting("http method lower case", method))
+		}
+	default:
+		notifyErrorListeners(p, expecting("http method", method))
+	}
+}
+
+func checkKeyword(p *ApiParserParser) {
+	v := getCurrentTokenText(p)
+	if IsGolangKeyWord(v) {
+		notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", v))
+	}
+}
+
+func checkKey(p *ApiParserParser) {
+	v := getCurrentTokenText(p)
+	if IsGolangKeyWord(v) {
+		notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", v))
+	}
+
+	if _, ok := kind[v]; !ok {
+		notifyErrorListeners(p, fmt.Sprintf("expecting golang basic type, found : '%s'", v))
+	}
+}
+
+func IsBasicType(text string) bool {
+	_, ok := kind[text]
+	return ok
+}
+
+func IsGolangKeyWord(text string, excepts ...string) bool {
+	for _, each := range excepts {
+		if text == each {
+			return false
+		}
+	}
+
+	switch text {
+	case "var", "const", "package", "import", "func", "return",
+		"defer", "go", "select", "interface", "struct", "break", "case",
+		"continue", "for", "fallthrough", "else", "if", "switch", "goto",
+		"default", "chan", "type", "map", "range":
+		return true
+	default:
+		return false
+	}
+}
+
+func isNormal(p *ApiParserParser) bool {
+	ct := p.GetTokenStream().(*antlr.CommonTokenStream)
+	line := p.GetCurrentToken().GetLine()
+	tokens := ct.GetAllTokens()
+	var list []string
+	for _, token := range tokens {
+		if token.GetLine() == line {
+			text := token.GetText()
+			if strings.HasPrefix(text, "//") {
+				continue
+			}
+			if strings.HasPrefix(text, "/*") {
+				continue
+			}
+			if text == "<EOF>" {
+				continue
+			}
+			if strings.TrimSpace(text) == "" {
+				continue
+			}
+			list = append(list, text)
+		}
+	}
+	if len(list) == 1 {
+		t := strings.TrimPrefix(list[0], "*")
+		if IsGolangKeyWord(t) {
+			notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", t))
+		}
+	}
+	if len(list) > 1 {
+		if list[0] == "*" {
+			t := strings.TrimPrefix(list[1], "*")
+			if IsGolangKeyWord(t) {
+				notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", t))
+			}
+			return false
+		}
+	}
+	return len(list) > 1
+}
+
+func MatchTag(v string) bool {
+	return matchRegex(v, tagRegex)
+}
+
+func isInterface(p *ApiParserParser) {
+	v := getCurrentTokenText(p)
+	if IsGolangKeyWord(v) {
+		notifyErrorListeners(p, fmt.Sprintf("expecting ID, found golang keyword: '%s'", v))
+	}
+}
+
+func getCurrentTokenText(p *ApiParserParser) string {
+	token := p.GetCurrentToken()
+	if token == nil {
+		return ""
+	}
+
+	return token.GetText()
+}
+
+func setCurrentTokenText(p *ApiParserParser, text string) {
+	token := p.GetCurrentToken()
+	if token == nil {
+		return
+	}
+
+	token.SetText(text)
+}
+
+func notifyErrorListeners(p *ApiParserParser, msg string) {
+	p.NotifyErrorListeners(msg, nil, nil)
+}
+
+func matchRegex(text, str string) bool {
+	re := regexp.MustCompile(str)
+	v := re.FindString(text)
+	text = strings.TrimFunc(text, func(r rune) bool {
+		return unicode.IsSpace(r)
+	})
+	return v == text
+}
+
+func expecting(expecting, found string) string {
+	return fmt.Sprintf(`expecting '%s', found input '%s'`, expecting, found)
+}
+
+func mismatched(expecting, found string) string {
+	return fmt.Sprintf(`mismatched '%s', found input '%s'`, expecting, found)
+}

+ 11 - 0
tools/goctl/api/parser/g4/gen/api/baseparser_test.go

@@ -0,0 +1,11 @@
+package api
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMatch(t *testing.T) {
+	assert.False(t, matchRegex("v1ddd", versionRegex))
+}

+ 99 - 0
tools/goctl/api/parser/g4/test.api

@@ -0,0 +1,99 @@
+// only one
+syntax = "v1"
+
+// import
+import "foo.api"
+
+// import group
+import(
+    "foo.api"
+    "foo/bar.api"
+)
+
+// only one
+info(
+    title: "foo title"
+    desc: "foo
+desc"
+    author: "foo author"
+    email: "foo email"
+    version: "foo version"
+)
+
+// ignore the following duplicate name
+
+type Foo int
+
+// type single
+type Foo {
+    Bar string
+}
+
+type Foo {
+    Bar string `json:"bar"`
+    Inline
+}
+// go struct
+type Foo struct {
+    Bar string `json:"bar"`
+}
+
+// type group
+type (
+    Foo int
+
+    // go struct
+    Foo struct {
+        Bar string `json:"bar"`
+    }
+
+    Foo {
+        VString string `json:"vString"`
+        VBool bool `json:"vBool"`
+        VInt8 int8 `json:"vInt8"`
+        VInt16 int16 `json:"vInt16"`
+        VInt32 int32 `json:"vInt32"`
+        VInt64 int64 `json:"vInt64"`
+        VInt int `json:"vInt"`
+        VUInt8 uint8 `json:"vUInt8"`
+        VUInt16 uint16 `json:"vUInt16"`
+        VUInt32 uint32 `json:"vUInt32"`
+        VUInt64 uint64 `json:"vUInt64"`
+        VFloat32 float32 `json:"vFloat32"`
+        VFloat64 float64 `json:"vFloat64"`
+        VByte byte `json:"vByte"`
+        VRune rune `json:"vRune"`
+        VMap map[string]int `json:"vMap"`
+        VArray []int `json:"vArray"`
+        VStruct Foo `json:"vStruct"`
+        VStructPointer *Foo `json:"vStructPointer"`
+        VInterface interface{} `json:"vInterface"`
+        T time.Time
+    }
+)
+
+@server(
+    jwt: Foo
+    group: foo/bar
+    anotherKey: anotherValue
+)
+service example-api {
+    @doc(
+        summary: "foo1"
+    )
+    @server(
+        handler: fooHandler1
+        anotherKey: anotherValue
+    )
+    post /api/foo1 (SingleExample)
+
+    @doc "foo2"
+    @handler fooHandler2
+    get /api/foo2 (SingleExample) returns (SingleExample2)
+
+    @handler fooHandler3
+    post /api/foo3/:id returns (SingleExample2)
+
+    @handler fooHandler4
+    get /api/foo4
+}

+ 369 - 0
tools/goctl/api/parser/g4/test/apiparser_test.go

@@ -0,0 +1,369 @@
+package test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
+)
+
+var (
+	normalApi = `
+	syntax="v1"
+	
+	info (
+		foo: bar
+	)
+
+	type Foo {
+		Bar int
+	}
+
+	@server(
+		foo: bar
+	)
+	service foo-api{
+		@doc("foo")
+		@handler foo
+		post /foo (Foo) returns ([]int)
+	}
+`
+	missDeclarationApi = `
+	@server(
+		foo: bar
+	)
+	service foo-api{
+		@doc("foo")
+		@handler foo
+		post /foo (Foo) returns (Foo)
+	}
+	`
+
+	missDeclarationInArrayApi = `
+	@server(
+		foo: bar
+	)
+	service foo-api{
+		@doc("foo")
+		@handler foo
+		post /foo returns ([]Foo)
+	}
+	`
+
+	missDeclarationInArrayApi2 = `
+	@server(
+		foo: bar
+	)
+	service foo-api{
+		@doc("foo")
+		@handler foo
+		post /foo returns ([]*Foo)
+	}
+	`
+
+	nestedApiImport = `
+	import "foo.api"
+	`
+
+	ambiguousSyntax = `
+	syntax = "v2"
+	`
+
+	ambiguousService = `
+	service bar-api{
+		@handler foo
+		post /foo
+	}
+	`
+	duplicateHandler = `
+	service bar-api{
+		@handler foo
+		post /foo
+	}
+	`
+
+	duplicateRoute = `
+	service bar-api{
+		@handler bar
+		post /foo
+	}
+	`
+
+	duplicateType = `
+	type Foo int
+	`
+)
+
+func TestApiParser(t *testing.T) {
+	t.Run("missDeclarationApi", func(t *testing.T) {
+		_, err := parser.ParseContent(missDeclarationApi)
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("missDeclarationApi", func(t *testing.T) {
+		_, err := parser.ParseContent(missDeclarationInArrayApi)
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("missDeclarationApi", func(t *testing.T) {
+		_, err := parser.ParseContent(missDeclarationInArrayApi2)
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("nestedImport", func(t *testing.T) {
+		file := filepath.Join(t.TempDir(), "foo.api")
+		err := ioutil.WriteFile(file, []byte(nestedApiImport), os.ModePerm)
+		if err != nil {
+			return
+		}
+
+		_, err = parser.ParseContent(fmt.Sprintf(`import "%s"`, file))
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("duplicateImport", func(t *testing.T) {
+		_, err := parser.ParseContent(`
+		import "foo.api"
+		import "foo.api"
+		`)
+		assert.Error(t, err)
+	})
+
+	t.Run("duplicateKey", func(t *testing.T) {
+		_, err := parser.ParseContent(`
+		info (
+			foo: bar
+			foo: bar
+		)
+		`)
+		assert.Error(t, err)
+	})
+
+	t.Run("ambiguousSyntax", func(t *testing.T) {
+		file := filepath.Join(t.TempDir(), "foo.api")
+		err := ioutil.WriteFile(file, []byte(ambiguousSyntax), os.ModePerm)
+		if err != nil {
+			return
+		}
+
+		_, err = parser.ParseContent(fmt.Sprintf(`
+		syntax = "v1"
+		import "%s"`, file))
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("ambiguousSyntax", func(t *testing.T) {
+		file := filepath.Join(t.TempDir(), "foo.api")
+		err := ioutil.WriteFile(file, []byte(ambiguousSyntax), os.ModePerm)
+		if err != nil {
+			return
+		}
+
+		_, err = parser.ParseContent(fmt.Sprintf(`
+		syntax = "v1"
+		import "%s"`, file))
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("ambiguousService", func(t *testing.T) {
+		file := filepath.Join(t.TempDir(), "foo.api")
+		err := ioutil.WriteFile(file, []byte(ambiguousService), os.ModePerm)
+		if err != nil {
+			return
+		}
+
+		_, err = parser.ParseContent(fmt.Sprintf(`
+		import "%s"
+		
+		service foo-api{
+			@handler foo
+			post /foo
+		}
+		`, file))
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("duplicateHandler", func(t *testing.T) {
+		_, err := parser.ParseContent(`
+		service foo-api{
+			@handler foo
+			post /foo
+			
+			@handler foo
+			post /bar
+		}
+		`)
+		assert.Error(t, err)
+
+		file := filepath.Join(t.TempDir(), "foo.api")
+		err = ioutil.WriteFile(file, []byte(duplicateHandler), os.ModePerm)
+		if err != nil {
+			return
+		}
+
+		_, err = parser.ParseContent(fmt.Sprintf(`
+		import "%s"
+		service bar-api{
+			@handler foo
+			post /foo
+		}
+		`, file))
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("duplicateRoute", func(t *testing.T) {
+		_, err := parser.ParseContent(`
+		service foo-api{
+			@handler foo
+			post /foo
+			
+			@handler bar
+			post /foo
+		}
+		`)
+		assert.Error(t, err)
+
+		file := filepath.Join(t.TempDir(), "foo.api")
+		err = ioutil.WriteFile(file, []byte(duplicateRoute), os.ModePerm)
+		if err != nil {
+			return
+		}
+
+		_, err = parser.ParseContent(fmt.Sprintf(`
+		import "%s"
+		service bar-api{
+			@handler foo
+			post /foo
+		}
+		`, file))
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("duplicateType", func(t *testing.T) {
+		_, err := parser.ParseContent(`
+		type Foo int
+		type Foo bool
+		`)
+		assert.Error(t, err)
+
+		file := filepath.Join(t.TempDir(), "foo.api")
+		err = ioutil.WriteFile(file, []byte(duplicateType), os.ModePerm)
+		if err != nil {
+			return
+		}
+
+		_, err = parser.ParseContent(fmt.Sprintf(`
+		import "%s"
+		
+		type Foo bool
+		`, file))
+		assert.Error(t, err)
+		fmt.Printf("%+v\n", err)
+	})
+
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.ParseContent(normalApi)
+		assert.Nil(t, err)
+		body := &ast.Body{
+			Lp:   ast.NewTextExpr("("),
+			Rp:   ast.NewTextExpr(")"),
+			Name: &ast.Literal{Literal: ast.NewTextExpr("Foo")},
+		}
+
+		assert.True(t, v.Equal(&ast.Api{
+			Syntax: &ast.SyntaxExpr{
+				Syntax:  ast.NewTextExpr("syntax"),
+				Assign:  ast.NewTextExpr("="),
+				Version: ast.NewTextExpr(`"v1"`),
+			},
+			Info: &ast.InfoExpr{
+				Info: ast.NewTextExpr("info"),
+				Lp:   ast.NewTextExpr("("),
+				Rp:   ast.NewTextExpr(")"),
+				Kvs: []*ast.KvExpr{
+					{
+						Key:   ast.NewTextExpr("foo"),
+						Value: ast.NewTextExpr("bar"),
+					},
+				},
+			},
+			Type: []ast.TypeExpr{
+				&ast.TypeStruct{
+					Name:   ast.NewTextExpr("Foo"),
+					LBrace: ast.NewTextExpr("{"),
+					RBrace: ast.NewTextExpr("}"),
+					Fields: []*ast.TypeField{
+						{
+							Name:     ast.NewTextExpr("Bar"),
+							DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
+						},
+					},
+				},
+			},
+			Service: []*ast.Service{
+				{
+					AtServer: &ast.AtServer{
+						AtServerToken: ast.NewTextExpr("@server"),
+						Lp:            ast.NewTextExpr("("),
+						Rp:            ast.NewTextExpr(")"),
+						Kv: []*ast.KvExpr{
+							{
+								Key:   ast.NewTextExpr("foo"),
+								Value: ast.NewTextExpr("bar"),
+							},
+						},
+					},
+					ServiceApi: &ast.ServiceApi{
+						ServiceToken: ast.NewTextExpr("service"),
+						Name:         ast.NewTextExpr("foo-api"),
+						Lbrace:       ast.NewTextExpr("{"),
+						Rbrace:       ast.NewTextExpr("}"),
+						ServiceRoute: []*ast.ServiceRoute{
+							{
+								AtDoc: &ast.AtDoc{
+									AtDocToken: ast.NewTextExpr("@doc"),
+									Lp:         ast.NewTextExpr("("),
+									Rp:         ast.NewTextExpr(")"),
+									LineDoc:    ast.NewTextExpr(`"foo"`),
+								},
+								AtHandler: &ast.AtHandler{
+									AtHandlerToken: ast.NewTextExpr("@handler"),
+									Name:           ast.NewTextExpr("foo"),
+								},
+								Route: &ast.Route{
+									Method:      ast.NewTextExpr("post"),
+									Path:        ast.NewTextExpr("/foo"),
+									Req:         body,
+									ReturnToken: ast.NewTextExpr("returns"),
+									Reply: &ast.Body{
+										Lp: ast.NewTextExpr("("),
+										Rp: ast.NewTextExpr(")"),
+										Name: &ast.Array{
+											ArrayExpr: ast.NewTextExpr("[]int"),
+											LBrack:    ast.NewTextExpr("["),
+											RBrack:    ast.NewTextExpr("]"),
+											Literal:   &ast.Literal{Literal: ast.NewTextExpr("int")},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		}))
+	})
+}

+ 0 - 0
tools/goctl/api/parser/g4/test/apis/empty.api


+ 72 - 0
tools/goctl/api/parser/g4/test/apis/example.api

@@ -0,0 +1,72 @@
+// syntax: specify the api syntax version,
+// through this version can be a good control
+// api syntax upgrade incompatibility issues
+syntax = "v1"
+
+// Info block is a key-value pair description body,
+// you can add some descriptions of the current api
+// file through this description body, you can add
+// any key-value pair, which does not participate in the api generation
+info(
+    title: sample of api
+    desc: "you can add a newline
+description by quotes"
+    author: songmeizi
+    anyAnotherKey: anyTnotherValue
+)
+
+// The structure in the api evolved from the structure of golang,
+// and it is also reserved to support the structure of golang.
+
+// a golang structure
+type Foo struct{
+    Foo int
+}
+
+// api structure
+type Bar {
+    Bar int
+}
+
+// structure group
+type (
+    FooBar {
+        Foo int
+        Bar bool
+    }
+)
+
+// Like the info block, @server can define any key-value pair.
+// The difference is that @server is a description of the service
+// block or route, which will participate in the api file generation.
+// There are several important keys that need to be understood,
+// which have special meanings. The jwt key is to declare that code
+// generation needs to include jwt authentication logic. The group key
+// is to declare that the files generated by the code need to be grouped
+// according to the value corresponding to the group. The handler key
+// determines the handler in golang. Layer file logic generation
+@server(
+    jwt: Auth
+    group: foo
+    anyAnotherKey: anyTnotherValue
+)
+
+// service block is the description of the api service,
+// including @doc block, @handler and api routing information
+service foo-api {
+    // shortening doc declaration
+    @doc("foo")
+    // shortening handler declaration
+    @handler foo
+    // route
+    get /foo (Foo) returns (Bar)
+
+
+    @doc(
+        summary: foo
+    )
+    @server(
+        handler: bar
+    )
+    post /bar (Foo)
+}

+ 6 - 0
tools/goctl/api/parser/g4/test/apis/info.api

@@ -0,0 +1,6 @@
+info(
+    author: songmeizi
+    desc: "the sample of
+info"
+    date: "2020-01-06"
+)

+ 29 - 0
tools/goctl/api/parser/g4/test/apis/service.api

@@ -0,0 +1,29 @@
+type Foo {}
+
+@server(
+    foo: foo
+    bar: "bar"
+    fooBar: "foo
+bar"
+)
+service foo-api {
+    @doc("foo")
+    @handler foo
+    get /foo (Foo) returns (Foo)
+    @handler bar
+    post /foo (Foo)
+    @handler fooBar
+    post /foo/bar
+    @server(
+        handler: getFoo
+    )
+    post /foo/:id returns(Foo)
+}
+
+service foo-api {
+    @doc(
+        summary:"post foo"
+    )
+    @handler postFoo
+    post /foo/bar/post (Foo)
+}

+ 1 - 0
tools/goctl/api/parser/g4/test/apis/syntax.api

@@ -0,0 +1 @@
+syntax = "v1"

+ 111 - 0
tools/goctl/api/parser/g4/test/apis/test.api

@@ -0,0 +1,111 @@
+// syntax doc
+syntax = "v1" // syntax comment
+
+// import doc
+import "foo.api" // import comment
+
+import(
+    // import group doc
+    "bar.api" // import group comment
+)
+
+// info doc
+info(// info comment
+    // author doc
+    author: "songmeizi" // author comment
+    // date doc
+    date: 2020-01-04 // date comment
+    // desc doc
+    desc: "break line
+    desc" // desc comment
+)
+
+
+type (
+    FooBar struct{
+        Foo int
+    }
+    // remove struct
+    Bar {
+        // vString
+        VString string `json:"vString"`
+        // vBool
+        VBool bool `json:"vBool"`
+        // vInt8
+        VInt8 int8 `json:"vInt8"`
+        // vInt16
+        VInt16 int16 `json:"vInt16"`
+        // vInt32
+        VInt32 int32 `json:"vInt32"`
+        // vInt64
+        VInt64 int64 `json:"vInt64"`
+        // vInt
+        VInt int `json:"vInt"`
+        // vUInt8
+        VUInt8 uint8 `json:"vUInt8"`
+        // vUInt16
+        VUInt16 uint16 `json:"vUInt16"`
+        // vUInt32
+        VUInt32 uint32 `json:"vUInt32"`
+        // vUInt64
+        VUInt64 uint64 `json:"vUInt64"`
+        // vFloat32
+        VFloat32 float32 `json:"vFloat32"`
+        // vFloat64
+        VFloat64 float64 `json:"vFloat64"`
+        // vByte
+        VByte byte `json:"vByte"`
+        // vRune
+        VRune rune `json:"vRune"`
+        // vMap
+        VMap map[string]int `json:"vMap"`
+        // vArray
+        VArray []int `json:"vArray"`
+        // vStruct
+        VStruct FooBar `json:"vStruct"`
+        // vStructPointer
+        VStructPointer *FooBar `json:"vStructPointer"`
+        // vInterface
+        VInterface interface{} `json:"vInterface"`
+        // inline
+        FooBar
+    }
+)
+
+@server(
+    host: 0.0.0.0
+    port: 8080
+    annotation: "break line
+    desc"
+)
+service foo-api{
+    @doc("foo")
+    @handler postFoo
+    // foo
+    post /foo (FooBar) returns (FooBar)
+
+    @doc(
+        summary: bar
+    )
+    @server(
+        handler: postBar
+    )
+    post /bar (FooBar)
+
+    @doc("foobar")
+    @handler postFooBar
+    /**
+    * httpmethod: post
+    * path: /foo/bar
+    * reply: FooBar
+    */
+    post /foo/bar returns (FooBar)
+
+    @doc("barfoo")
+    @handler postBarFoo
+    post /bar/foo // post:/bar/foo
+
+    @doc("barfoo")
+    @handler getBarFoo
+    get /bar/foo returns (FooBar)
+}

+ 23 - 0
tools/goctl/api/parser/g4/test/apis/types.api

@@ -0,0 +1,23 @@
+type Foo{
+}
+
+type Bar struct{
+}
+
+type FooBar {
+    Foo int
+    Bar bool
+    Map map[string]int
+    Map1 map[string]Bar
+    Map2 map[string]*Bar
+    Map3 map[string][]int
+    Map4 map[string][]Bar
+    Map5 map[string][]*Bar
+    Map6 map[string]map[string]int
+    Array []int
+    Array1 []*Bar
+    Array2 []Bar
+    Pointer *Bar
+    Bar
+}
+

+ 457 - 0
tools/goctl/api/parser/g4/test/ast_test.go

@@ -0,0 +1,457 @@
+package test
+
+import (
+	"io/ioutil"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+var parser = ast.NewParser(ast.WithParserPrefix("test.api"), ast.WithParserDebug())
+
+func TestApi(t *testing.T) {
+	fn := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.Api().Accept(visitor)
+	}
+	content, err := ioutil.ReadFile("./apis/test.api")
+	assert.Nil(t, err)
+
+	v, err := parser.Accept(fn, string(content))
+	assert.Nil(t, err)
+	api := v.(*ast.Api)
+	body := &ast.Body{
+		Lp:   ast.NewTextExpr("("),
+		Rp:   ast.NewTextExpr(")"),
+		Name: &ast.Literal{Literal: ast.NewTextExpr("FooBar")},
+	}
+
+	returns := ast.NewTextExpr("returns")
+	assert.True(t, api.Equal(&ast.Api{
+		Syntax: &ast.SyntaxExpr{
+			Syntax:  ast.NewTextExpr("syntax"),
+			Assign:  ast.NewTextExpr("="),
+			Version: ast.NewTextExpr(`"v1"`),
+			DocExpr: []ast.Expr{
+				ast.NewTextExpr("// syntax doc"),
+			},
+			CommentExpr: ast.NewTextExpr("// syntax comment"),
+		},
+		Import: []*ast.ImportExpr{
+			{
+				Import: ast.NewTextExpr("import"),
+				Value:  ast.NewTextExpr(`"foo.api"`),
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("// import doc"),
+				},
+				CommentExpr: ast.NewTextExpr("// import comment"),
+			},
+			{
+				Import: ast.NewTextExpr("import"),
+				Value:  ast.NewTextExpr(`"bar.api"`),
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("// import group doc"),
+				},
+				CommentExpr: ast.NewTextExpr("// import group comment"),
+			},
+		},
+		Info: &ast.InfoExpr{
+			Info: ast.NewTextExpr("info"),
+			Lp:   ast.NewTextExpr("("),
+			Rp:   ast.NewTextExpr(")"),
+			Kvs: []*ast.KvExpr{
+				{
+					Key:   ast.NewTextExpr("author"),
+					Value: ast.NewTextExpr(`"songmeizi"`),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// author doc"),
+					},
+					CommentExpr: ast.NewTextExpr("// author comment"),
+				},
+				{
+					Key:   ast.NewTextExpr("date"),
+					Value: ast.NewTextExpr(`2020-01-04`),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// date doc"),
+					},
+					CommentExpr: ast.NewTextExpr("// date comment"),
+				},
+				{
+					Key: ast.NewTextExpr("desc"),
+					Value: ast.NewTextExpr(`"break line
+    desc"`),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// desc doc"),
+					},
+					CommentExpr: ast.NewTextExpr("// desc comment"),
+				},
+			},
+		},
+		Type: []ast.TypeExpr{
+			&ast.TypeStruct{
+				Name:   ast.NewTextExpr("FooBar"),
+				Struct: ast.NewTextExpr("struct"),
+				LBrace: ast.NewTextExpr("{"),
+				RBrace: ast.NewTextExpr("}"),
+				Fields: []*ast.TypeField{
+					{
+						Name:     ast.NewTextExpr("Foo"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
+					},
+				},
+			},
+			&ast.TypeStruct{
+				Name:   ast.NewTextExpr("Bar"),
+				LBrace: ast.NewTextExpr("{"),
+				RBrace: ast.NewTextExpr("}"),
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("// remove struct"),
+				},
+				Fields: []*ast.TypeField{
+					{
+						Name:     ast.NewTextExpr("VString"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("string")},
+						Tag:      ast.NewTextExpr("`json:\"vString\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vString"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VBool"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("bool")},
+						Tag:      ast.NewTextExpr("`json:\"vBool\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vBool"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VInt8"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("int8")},
+						Tag:      ast.NewTextExpr("`json:\"vInt8\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vInt8"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VInt16"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("int16")},
+						Tag:      ast.NewTextExpr("`json:\"vInt16\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vInt16"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VInt32"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("int32")},
+						Tag:      ast.NewTextExpr("`json:\"vInt32\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vInt32"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VInt64"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("int64")},
+						Tag:      ast.NewTextExpr("`json:\"vInt64\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vInt64"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VInt"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
+						Tag:      ast.NewTextExpr("`json:\"vInt\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vInt"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VUInt8"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("uint8")},
+						Tag:      ast.NewTextExpr("`json:\"vUInt8\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vUInt8"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VUInt16"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("uint16")},
+						Tag:      ast.NewTextExpr("`json:\"vUInt16\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vUInt16"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VUInt32"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("uint32")},
+						Tag:      ast.NewTextExpr("`json:\"vUInt32\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vUInt32"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VUInt64"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("uint64")},
+						Tag:      ast.NewTextExpr("`json:\"vUInt64\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vUInt64"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VFloat32"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("float32")},
+						Tag:      ast.NewTextExpr("`json:\"vFloat32\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vFloat32"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VFloat64"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("float64")},
+						Tag:      ast.NewTextExpr("`json:\"vFloat64\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vFloat64"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VByte"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("byte")},
+						Tag:      ast.NewTextExpr("`json:\"vByte\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vByte"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VRune"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("rune")},
+						Tag:      ast.NewTextExpr("`json:\"vRune\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vRune"),
+						},
+					},
+					{
+						Name: ast.NewTextExpr("VMap"),
+						DataType: &ast.Map{
+							MapExpr: ast.NewTextExpr("map[string]int"),
+							Map:     ast.NewTextExpr("map"),
+							LBrack:  ast.NewTextExpr("["),
+							RBrack:  ast.NewTextExpr("]"),
+							Key:     ast.NewTextExpr("string"),
+							Value:   &ast.Literal{Literal: ast.NewTextExpr("int")},
+						},
+						Tag: ast.NewTextExpr("`json:\"vMap\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vMap"),
+						},
+					},
+					{
+						Name: ast.NewTextExpr("VArray"),
+						DataType: &ast.Array{
+							ArrayExpr: ast.NewTextExpr("[]int"),
+							LBrack:    ast.NewTextExpr("["),
+							RBrack:    ast.NewTextExpr("]"),
+							Literal:   &ast.Literal{Literal: ast.NewTextExpr("int")},
+						},
+						Tag: ast.NewTextExpr("`json:\"vArray\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vArray"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VStruct"),
+						DataType: &ast.Literal{Literal: ast.NewTextExpr("FooBar")},
+						Tag:      ast.NewTextExpr("`json:\"vStruct\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vStruct"),
+						},
+					},
+					{
+						Name: ast.NewTextExpr("VStructPointer"),
+						DataType: &ast.Pointer{
+							PointerExpr: ast.NewTextExpr("*FooBar"),
+							Star:        ast.NewTextExpr("*"),
+							Name:        ast.NewTextExpr("FooBar"),
+						},
+						Tag: ast.NewTextExpr("`json:\"vStructPointer\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vStructPointer"),
+						},
+					},
+					{
+						Name:     ast.NewTextExpr("VInterface"),
+						DataType: &ast.Interface{Literal: ast.NewTextExpr("interface{}")},
+						Tag:      ast.NewTextExpr("`json:\"vInterface\"`"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// vInterface"),
+						},
+					},
+					{
+						IsAnonymous: true,
+						DataType:    &ast.Literal{Literal: ast.NewTextExpr("FooBar")},
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// inline"),
+						},
+					},
+				},
+			},
+		},
+		Service: []*ast.Service{
+			{
+				AtServer: &ast.AtServer{
+					AtServerToken: ast.NewTextExpr("@server"),
+					Lp:            ast.NewTextExpr("("),
+					Rp:            ast.NewTextExpr(")"),
+					Kv: []*ast.KvExpr{
+						{
+							Key:   ast.NewTextExpr("host"),
+							Value: ast.NewTextExpr("0.0.0.0"),
+						},
+						{
+							Key:   ast.NewTextExpr("port"),
+							Value: ast.NewTextExpr("8080"),
+						},
+						{
+							Key: ast.NewTextExpr("annotation"),
+							Value: ast.NewTextExpr(`"break line
+    desc"`),
+						},
+					},
+				},
+				ServiceApi: &ast.ServiceApi{
+					ServiceToken: ast.NewTextExpr("service"),
+					Name:         ast.NewTextExpr("foo-api"),
+					Lbrace:       ast.NewTextExpr("{"),
+					Rbrace:       ast.NewTextExpr("}"),
+					ServiceRoute: []*ast.ServiceRoute{
+						{
+							AtDoc: &ast.AtDoc{
+								AtDocToken: ast.NewTextExpr("@doc"),
+								Lp:         ast.NewTextExpr("("),
+								Rp:         ast.NewTextExpr(")"),
+								LineDoc:    ast.NewTextExpr(`"foo"`),
+							},
+							AtHandler: &ast.AtHandler{
+								AtHandlerToken: ast.NewTextExpr("@handler"),
+								Name:           ast.NewTextExpr("postFoo"),
+							},
+							Route: &ast.Route{
+								Method:      ast.NewTextExpr("post"),
+								Path:        ast.NewTextExpr("/foo"),
+								Req:         body,
+								ReturnToken: returns,
+								Reply:       body,
+								DocExpr: []ast.Expr{
+									ast.NewTextExpr("// foo"),
+								},
+							},
+						},
+						{
+							AtDoc: &ast.AtDoc{
+								AtDocToken: ast.NewTextExpr("@doc"),
+								Lp:         ast.NewTextExpr("("),
+								Rp:         ast.NewTextExpr(")"),
+								Kv: []*ast.KvExpr{
+									{
+										Key:   ast.NewTextExpr("summary"),
+										Value: ast.NewTextExpr("bar"),
+									},
+								},
+							},
+							AtServer: &ast.AtServer{
+								AtServerToken: ast.NewTextExpr("@server"),
+								Lp:            ast.NewTextExpr("("),
+								Rp:            ast.NewTextExpr(")"),
+								Kv: []*ast.KvExpr{
+									{
+										Key:   ast.NewTextExpr("handler"),
+										Value: ast.NewTextExpr("postBar"),
+									},
+								},
+							},
+							Route: &ast.Route{
+								Method: ast.NewTextExpr("post"),
+								Path:   ast.NewTextExpr("/bar"),
+								Req:    body,
+							},
+						},
+						{
+							AtDoc: &ast.AtDoc{
+								AtDocToken: ast.NewTextExpr("@doc"),
+								Lp:         ast.NewTextExpr("("),
+								Rp:         ast.NewTextExpr(")"),
+								LineDoc:    ast.NewTextExpr(`"foobar"`),
+							},
+							AtHandler: &ast.AtHandler{
+								AtHandlerToken: ast.NewTextExpr("@handler"),
+								Name:           ast.NewTextExpr("postFooBar"),
+							},
+							Route: &ast.Route{
+								Method:      ast.NewTextExpr("post"),
+								Path:        ast.NewTextExpr("/foo/bar"),
+								ReturnToken: returns,
+								Reply:       body,
+								DocExpr: []ast.Expr{
+									ast.NewTextExpr(`/**
+    * httpmethod: post
+    * path: /foo/bar
+    * reply: FooBar
+    */`),
+								},
+							},
+						},
+						{
+							AtDoc: &ast.AtDoc{
+								AtDocToken: ast.NewTextExpr("@doc"),
+								Lp:         ast.NewTextExpr("("),
+								Rp:         ast.NewTextExpr(")"),
+								LineDoc:    ast.NewTextExpr(`"barfoo"`),
+							},
+							AtHandler: &ast.AtHandler{
+								AtHandlerToken: ast.NewTextExpr("@handler"),
+								Name:           ast.NewTextExpr("postBarFoo"),
+							},
+							Route: &ast.Route{
+								Method:      ast.NewTextExpr("post"),
+								Path:        ast.NewTextExpr("/bar/foo"),
+								ReturnToken: returns,
+								Reply:       body,
+								CommentExpr: ast.NewTextExpr("// post:/bar/foo"),
+							},
+						},
+						{
+							AtDoc: &ast.AtDoc{
+								AtDocToken: ast.NewTextExpr("@doc"),
+								Lp:         ast.NewTextExpr("("),
+								Rp:         ast.NewTextExpr(")"),
+								LineDoc:    ast.NewTextExpr(`"barfoo"`),
+							},
+							AtHandler: &ast.AtHandler{
+								AtHandlerToken: ast.NewTextExpr("@handler"),
+								Name:           ast.NewTextExpr("getBarFoo"),
+							},
+							Route: &ast.Route{
+								Method:      ast.NewTextExpr("get"),
+								Path:        ast.NewTextExpr("/bar/foo"),
+								ReturnToken: returns,
+								Reply:       body,
+							},
+						},
+					},
+				},
+			},
+		},
+	}))
+}
+
+func TestApiSyntax(t *testing.T) {
+	fn := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.Api().Accept(visitor)
+	}
+	parser.Accept(fn, `
+	// doc 1
+	// doc 2
+	syntax = "v1" // comment 1
+	// comment 2
+	import "foo.api"
+	`)
+}

+ 25 - 0
tools/goctl/api/parser/g4/test/grammar_test.go

@@ -0,0 +1,25 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var files = []string{
+	"example",
+	"empty",
+	"syntax",
+	"info",
+	"types",
+	"service",
+}
+
+func TestGrammar(t *testing.T) {
+	for _, file := range files {
+		t.Run(file, func(t *testing.T) {
+			_, err := parser.Parse("./apis/" + file + ".api")
+			assert.Nil(t, err)
+		})
+	}
+}

+ 143 - 0
tools/goctl/api/parser/g4/test/import_test.go

@@ -0,0 +1,143 @@
+package test
+
+import (
+	"sort"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+var importAccept = func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+	return p.ImportSpec().Accept(visitor)
+}
+
+func TestImport(t *testing.T) {
+	t.Run("matched", func(t *testing.T) {
+		v, err := parser.Accept(importAccept, `import "foo.api"`)
+		assert.Nil(t, err)
+
+		list := v.([]*ast.ImportExpr)
+		for _, each := range list {
+			assert.True(t, each.Equal(&ast.ImportExpr{
+				Import: ast.NewTextExpr("import"),
+				Value:  ast.NewTextExpr(`"foo.api"`),
+			}))
+		}
+	})
+
+	t.Run("matched block", func(t *testing.T) {
+		v, err := parser.Accept(importAccept, `
+		import (
+			/**foo*/
+			"foo.api"
+			/**bar*/
+			"bar.api"
+			/**foobar*/
+			"foo/bar.api"/**foobar*/
+		)
+		`)
+		assert.Nil(t, err)
+
+		list := v.([]*ast.ImportExpr)
+		expected := []*ast.ImportExpr{
+			{
+				Import: ast.NewTextExpr("import"),
+				Value:  ast.NewTextExpr(`"foo.api"`),
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("/**foo*/"),
+				},
+			},
+			{
+				Import: ast.NewTextExpr("import"),
+				Value:  ast.NewTextExpr(`"bar.api"`),
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("/**bar*/"),
+				},
+			},
+			{
+				Import: ast.NewTextExpr("import"),
+				Value:  ast.NewTextExpr(`"foo/bar.api"`),
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("/**foobar*/"),
+				},
+				CommentExpr: ast.NewTextExpr("/**foobar*/"),
+			},
+		}
+
+		sort.Slice(list, func(i, j int) bool {
+			return list[i].Value.Line() < list[j].Value.Line()
+		})
+		sort.Slice(expected, func(i, j int) bool {
+			return expected[i].Value.Line() < expected[j].Value.Line()
+		})
+
+		assert.True(t, len(list) == len(expected))
+		for index, each := range list {
+			assert.True(t, each.Equal(expected[index]))
+		}
+	})
+
+	t.Run("matched doc", func(t *testing.T) {
+		v, err := parser.Accept(importAccept, `
+		/**doc*/
+		import "foo.api" /**line doc*/`)
+		assert.Nil(t, err)
+
+		list := v.([]*ast.ImportExpr)
+		for _, each := range list {
+			assert.True(t, each.Equal(&ast.ImportExpr{
+				Import: ast.NewTextExpr("import"),
+				Value:  ast.NewTextExpr(`"foo.api"`),
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("/**doc*/"),
+				},
+				CommentExpr: ast.NewTextExpr("/**line doc*/"),
+			}))
+		}
+	})
+
+	t.Run("matched comment", func(t *testing.T) {
+		v, err := parser.Accept(importAccept, `
+		// comment block
+		import "foo.api" // line comment`)
+		assert.Nil(t, err)
+
+		list := v.([]*ast.ImportExpr)
+		for _, each := range list {
+			assert.True(t, each.Equal(&ast.ImportExpr{
+				Import: ast.NewTextExpr("import"),
+				Value:  ast.NewTextExpr(`"foo.api"`),
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("// comment block"),
+				},
+				CommentExpr: ast.NewTextExpr("// line comment"),
+			}))
+		}
+	})
+
+	t.Run("mismatched import", func(t *testing.T) {
+		_, err := parser.Accept(importAccept, `
+		 "foo.api"`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(importAccept, `
+		 impor "foo.api"`)
+		assert.Error(t, err)
+	})
+
+	t.Run("mismatched value", func(t *testing.T) {
+		_, err := parser.Accept(importAccept, `
+		 import "foo"`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(importAccept, `
+		 import ""`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(importAccept, `
+		 import `)
+		assert.Error(t, err)
+	})
+}

+ 186 - 0
tools/goctl/api/parser/g4/test/info_test.go

@@ -0,0 +1,186 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+var infoAccept = func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+	return p.InfoSpec().Accept(visitor)
+}
+
+func TestInfo(t *testing.T) {
+	t.Run("matched", func(t *testing.T) {
+		v, err := parser.Accept(infoAccept, `
+			info(
+				title: foo
+			)
+		`)
+
+		assert.Nil(t, err)
+		info := v.(*ast.InfoExpr)
+		assert.True(t, info.Equal(&ast.InfoExpr{
+			Info: ast.NewTextExpr("info"),
+			Kvs: []*ast.KvExpr{
+				{
+					Key:   ast.NewTextExpr("title"),
+					Value: ast.NewTextExpr("foo"),
+				},
+			},
+		}))
+
+		v, err = parser.Accept(infoAccept, `
+			info(
+				title: 中文(bar)
+			)
+		`)
+		assert.Nil(t, err)
+		info = v.(*ast.InfoExpr)
+		assert.True(t, info.Equal(&ast.InfoExpr{
+			Info: ast.NewTextExpr("info"),
+			Kvs: []*ast.KvExpr{
+				{
+					Key:   ast.NewTextExpr("title"),
+					Value: ast.NewTextExpr("中文(bar)"),
+				},
+			},
+		}))
+
+		v, err = parser.Accept(infoAccept, `
+			info(
+				foo: "new
+line"
+			)
+		`)
+		assert.Nil(t, err)
+		info = v.(*ast.InfoExpr)
+		assert.True(t, info.Equal(&ast.InfoExpr{
+			Info: ast.NewTextExpr("info"),
+			Kvs: []*ast.KvExpr{
+				{
+					Key: ast.NewTextExpr("foo"),
+					Value: ast.NewTextExpr(`"new
+line"`),
+				},
+			},
+		}))
+	})
+
+	t.Run("matched doc", func(t *testing.T) {
+		v, err := parser.Accept(infoAccept, `
+			// doc
+			info( // comment
+				// foo
+				title: foo // bar
+			)
+		`)
+		assert.Nil(t, err)
+		info := v.(*ast.InfoExpr)
+		assert.True(t, info.Equal(&ast.InfoExpr{
+			Info: ast.NewTextExpr("info"),
+			Kvs: []*ast.KvExpr{
+				{
+					Key:   ast.NewTextExpr("title"),
+					Value: ast.NewTextExpr("foo"),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// foo"),
+					},
+					CommentExpr: ast.NewTextExpr("// bar"),
+				},
+			},
+		}))
+
+		v, err = parser.Accept(infoAccept, `
+			/**doc block*/
+			info( /**line block*/
+				/**foo*/
+				title: foo /*bar**/
+			)
+		`)
+		assert.Nil(t, err)
+		info = v.(*ast.InfoExpr)
+		assert.True(t, info.Equal(&ast.InfoExpr{
+			Info: ast.NewTextExpr("info"),
+			Kvs: []*ast.KvExpr{
+				{
+					Key:   ast.NewTextExpr("title"),
+					Value: ast.NewTextExpr("foo"),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("/**foo*/"),
+					},
+					CommentExpr: ast.NewTextExpr("/*bar**/"),
+				},
+			},
+		}))
+
+		v, err = parser.Accept(infoAccept, `
+			info( 
+				// doc
+				title: foo 
+				// doc
+				author: bar
+			)
+		`)
+		assert.Nil(t, err)
+		info = v.(*ast.InfoExpr)
+		assert.True(t, info.Equal(&ast.InfoExpr{
+			Info: ast.NewTextExpr("info"),
+			Kvs: []*ast.KvExpr{
+				{
+					Key:   ast.NewTextExpr("title"),
+					Value: ast.NewTextExpr("foo"),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// doc"),
+					},
+				},
+				{
+					Key:   ast.NewTextExpr("author"),
+					Value: ast.NewTextExpr("bar"),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// doc"),
+					},
+				},
+			},
+		}))
+
+	})
+
+	t.Run("mismatched", func(t *testing.T) {
+		_, err := parser.Accept(infoAccept, `
+			info(
+				title
+			)
+		`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(infoAccept, `
+			info(
+				:title
+			)
+		`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(infoAccept, `
+			info(
+				foo bar
+			)
+		`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(infoAccept, `
+			info(
+				foo : new 
+line
+			)
+		`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(infoAccept, `
+			info()
+		`)
+		assert.Error(t, err)
+	})
+}

+ 686 - 0
tools/goctl/api/parser/g4/test/service_test.go

@@ -0,0 +1,686 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+func TestBody(t *testing.T) {
+	fn := func(p *api.ApiParserParser, v *ast.ApiVisitor) interface{} {
+		return p.Body().Accept(v)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fn, `(Foo)`)
+		assert.Nil(t, err)
+		body := v.(*ast.Body)
+		assert.True(t, body.Equal(&ast.Body{
+			Lp:   ast.NewTextExpr("("),
+			Rp:   ast.NewTextExpr(")"),
+			Name: &ast.Literal{Literal: ast.NewTextExpr("Foo")},
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(fn, `(var)`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `()`)
+		assert.Nil(t, err)
+	})
+}
+
+func TestRoute(t *testing.T) {
+	fn := func(p *api.ApiParserParser, v *ast.ApiVisitor) interface{} {
+		return p.Route().Accept(v)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fn, `post /foo/foo-bar/:bar (Foo) returns (Bar)`)
+		assert.Nil(t, err)
+		route := v.(*ast.Route)
+		assert.True(t, route.Equal(&ast.Route{
+			Method: ast.NewTextExpr("post"),
+			Path:   ast.NewTextExpr("/foo/foo-bar/:bar"),
+			Req: &ast.Body{
+				Lp:   ast.NewTextExpr("("),
+				Rp:   ast.NewTextExpr(")"),
+				Name: &ast.Literal{Literal: ast.NewTextExpr("Foo")},
+			},
+			ReturnToken: ast.NewTextExpr("returns"),
+			Reply: &ast.Body{
+				Lp:   ast.NewTextExpr("("),
+				Rp:   ast.NewTextExpr(")"),
+				Name: &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+			},
+		}))
+
+		v, err = parser.Accept(fn, `post /foo/foo-bar/:bar (Foo)`)
+		assert.Nil(t, err)
+		route = v.(*ast.Route)
+		assert.True(t, route.Equal(&ast.Route{
+			Method: ast.NewTextExpr("post"),
+			Path:   ast.NewTextExpr("/foo/foo-bar/:bar"),
+			Req: &ast.Body{
+				Lp:   ast.NewTextExpr("("),
+				Rp:   ast.NewTextExpr(")"),
+				Name: &ast.Literal{Literal: ast.NewTextExpr("Foo")},
+			},
+		}))
+
+		v, err = parser.Accept(fn, `post /foo/foo-bar/:bar returns (Bar)`)
+		assert.Nil(t, err)
+		route = v.(*ast.Route)
+		assert.True(t, route.Equal(&ast.Route{
+			Method:      ast.NewTextExpr("post"),
+			Path:        ast.NewTextExpr("/foo/foo-bar/:bar"),
+			ReturnToken: ast.NewTextExpr("returns"),
+			Reply: &ast.Body{
+				Lp:   ast.NewTextExpr("("),
+				Rp:   ast.NewTextExpr(")"),
+				Name: &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+			},
+		}))
+
+		v, err = parser.Accept(fn, `post /foo/foo-bar/:bar returns ([]Bar)`)
+		assert.Nil(t, err)
+		route = v.(*ast.Route)
+		assert.True(t, route.Equal(&ast.Route{
+			Method:      ast.NewTextExpr("post"),
+			Path:        ast.NewTextExpr("/foo/foo-bar/:bar"),
+			ReturnToken: ast.NewTextExpr("returns"),
+			Reply: &ast.Body{
+				Lp: ast.NewTextExpr("("),
+				Rp: ast.NewTextExpr(")"),
+				Name: &ast.Array{
+					ArrayExpr: ast.NewTextExpr("[]Bar"),
+					LBrack:    ast.NewTextExpr("["),
+					RBrack:    ast.NewTextExpr("]"),
+					Literal:   &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+				},
+			},
+		}))
+
+		v, err = parser.Accept(fn, `post /foo/foo-bar/:bar returns ([]*Bar)`)
+		assert.Nil(t, err)
+		route = v.(*ast.Route)
+		assert.True(t, route.Equal(&ast.Route{
+			Method:      ast.NewTextExpr("post"),
+			Path:        ast.NewTextExpr("/foo/foo-bar/:bar"),
+			ReturnToken: ast.NewTextExpr("returns"),
+			Reply: &ast.Body{
+				Lp: ast.NewTextExpr("("),
+				Rp: ast.NewTextExpr(")"),
+				Name: &ast.Array{
+					ArrayExpr: ast.NewTextExpr("[]*Bar"),
+					LBrack:    ast.NewTextExpr("["),
+					RBrack:    ast.NewTextExpr("]"),
+					Literal: &ast.Pointer{
+						PointerExpr: ast.NewTextExpr("*Bar"),
+						Star:        ast.NewTextExpr("*"),
+						Name:        ast.NewTextExpr("Bar"),
+					}},
+			},
+		}))
+
+		v, err = parser.Accept(fn, `post /foo/foo-bar/:bar`)
+		assert.Nil(t, err)
+		route = v.(*ast.Route)
+		assert.True(t, route.Equal(&ast.Route{
+			Method: ast.NewTextExpr("post"),
+			Path:   ast.NewTextExpr("/foo/foo-bar/:bar"),
+		}))
+
+		v, err = parser.Accept(fn, `
+		// foo
+		post /foo/foo-bar/:bar // bar`)
+		assert.Nil(t, err)
+		route = v.(*ast.Route)
+		assert.True(t, route.Equal(&ast.Route{
+			Method: ast.NewTextExpr("post"),
+			Path:   ast.NewTextExpr("/foo/foo-bar/:bar"),
+			DocExpr: []ast.Expr{
+				ast.NewTextExpr("// foo"),
+			},
+			CommentExpr: ast.NewTextExpr("// bar"),
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(fn, `posts /foo`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `gets /foo`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `post /foo/:`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `post /foo/`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `post foo/bar`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `post /foo/bar return (Bar)`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, ` /foo/bar returns (Bar)`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, ` post   returns (Bar)`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, ` post /foo/bar returns (int)`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, ` post /foo/bar returns (*int)`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, ` post /foo/bar returns ([]var)`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, ` post /foo/bar returns (const)`)
+		assert.Error(t, err)
+	})
+}
+
+func TestAtHandler(t *testing.T) {
+	fn := func(p *api.ApiParserParser, v *ast.ApiVisitor) interface{} {
+		return p.AtHandler().Accept(v)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fn, `@handler foo`)
+		assert.Nil(t, err)
+		atHandler := v.(*ast.AtHandler)
+		assert.True(t, atHandler.Equal(&ast.AtHandler{
+			AtHandlerToken: ast.NewTextExpr("@handler"),
+			Name:           ast.NewTextExpr("foo"),
+		}))
+
+		v, err = parser.Accept(fn, `
+		// foo
+		@handler foo // bar`)
+		assert.Nil(t, err)
+		atHandler = v.(*ast.AtHandler)
+		assert.True(t, atHandler.Equal(&ast.AtHandler{
+			AtHandlerToken: ast.NewTextExpr("@handler"),
+			Name:           ast.NewTextExpr("foo"),
+			DocExpr: []ast.Expr{
+				ast.NewTextExpr("// foo"),
+			},
+			CommentExpr: ast.NewTextExpr("// bar"),
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(fn, ``)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `@handler`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `@handler "foo"`)
+		assert.Error(t, err)
+	})
+
+}
+
+func TestAtDoc(t *testing.T) {
+	fn := func(p *api.ApiParserParser, v *ast.ApiVisitor) interface{} {
+		return p.AtDoc().Accept(v)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fn, `@doc "foo"`)
+		assert.Nil(t, err)
+		atDoc := v.(*ast.AtDoc)
+		assert.True(t, atDoc.Equal(&ast.AtDoc{
+			AtDocToken: ast.NewTextExpr("@doc"),
+			LineDoc:    ast.NewTextExpr(`"foo"`),
+		}))
+
+		v, err = parser.Accept(fn, `@doc("foo")`)
+		assert.Nil(t, err)
+		atDoc = v.(*ast.AtDoc)
+		assert.True(t, atDoc.Equal(&ast.AtDoc{
+			AtDocToken: ast.NewTextExpr("@doc"),
+			Lp:         ast.NewTextExpr("("),
+			Rp:         ast.NewTextExpr(")"),
+			LineDoc:    ast.NewTextExpr(`"foo"`),
+		}))
+
+		v, err = parser.Accept(fn, `@doc(
+			foo: bar
+		)`)
+		assert.Nil(t, err)
+		atDoc = v.(*ast.AtDoc)
+		assert.True(t, atDoc.Equal(&ast.AtDoc{
+			AtDocToken: ast.NewTextExpr("@doc"),
+			Lp:         ast.NewTextExpr("("),
+			Rp:         ast.NewTextExpr(")"),
+			Kv: []*ast.KvExpr{
+				{
+					Key:   ast.NewTextExpr("foo"),
+					Value: ast.NewTextExpr("bar"),
+				},
+			},
+		}))
+
+		v, err = parser.Accept(fn, `@doc(
+			// foo
+			foo: bar // bar
+		)`)
+		assert.Nil(t, err)
+		atDoc = v.(*ast.AtDoc)
+		assert.True(t, atDoc.Equal(&ast.AtDoc{
+			AtDocToken: ast.NewTextExpr("@doc"),
+			Lp:         ast.NewTextExpr("("),
+			Rp:         ast.NewTextExpr(")"),
+			Kv: []*ast.KvExpr{
+				{
+					Key:   ast.NewTextExpr("foo"),
+					Value: ast.NewTextExpr("bar"),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// foo"),
+					},
+					CommentExpr: ast.NewTextExpr("// bar"),
+				},
+			},
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(fn, `@doc("foo"`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `@doc "foo")`)
+		assert.Error(t, err)
+	})
+}
+
+func TestServiceRoute(t *testing.T) {
+	fn := func(p *api.ApiParserParser, v *ast.ApiVisitor) interface{} {
+		return p.ServiceRoute().Accept(v)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fn, `
+		@doc("foo")
+		// foo/bar
+		// foo
+		@handler foo // bar
+		// foo/bar
+		// foo
+		post /foo (Foo) returns (Bar) // bar
+		`)
+		assert.Nil(t, err)
+		sr := v.(*ast.ServiceRoute)
+		assert.True(t, sr.Equal(&ast.ServiceRoute{
+			AtDoc: &ast.AtDoc{
+				AtDocToken: ast.NewTextExpr("@doc"),
+				Lp:         ast.NewTextExpr("("),
+				Rp:         ast.NewTextExpr(")"),
+				LineDoc:    ast.NewTextExpr(`"foo"`),
+			},
+			AtHandler: &ast.AtHandler{
+				AtHandlerToken: ast.NewTextExpr("@handler"),
+				Name:           ast.NewTextExpr("foo"),
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("// foo"),
+				},
+				CommentExpr: ast.NewTextExpr("// bar"),
+			},
+			Route: &ast.Route{
+				Method: ast.NewTextExpr("post"),
+				Path:   ast.NewTextExpr("/foo"),
+				Req: &ast.Body{
+					Lp:   ast.NewTextExpr("("),
+					Rp:   ast.NewTextExpr(")"),
+					Name: &ast.Literal{Literal: ast.NewTextExpr("Foo")},
+				},
+				ReturnToken: ast.NewTextExpr("returns"),
+				Reply: &ast.Body{
+					Lp:   ast.NewTextExpr("("),
+					Rp:   ast.NewTextExpr(")"),
+					Name: &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+				},
+				DocExpr: []ast.Expr{
+					ast.NewTextExpr("// foo"),
+				},
+				CommentExpr: ast.NewTextExpr("// bar"),
+			},
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(fn, `post /foo (Foo) returns (Bar) // bar`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `@handler foo`)
+		assert.Error(t, err)
+	})
+}
+
+func TestServiceApi(t *testing.T) {
+	fn := func(p *api.ApiParserParser, v *ast.ApiVisitor) interface{} {
+		return p.ServiceApi().Accept(v)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fn, `
+		service foo-api{
+			@doc("foo")
+			// foo/bar
+			// foo
+			@handler foo // bar
+			// foo/bar
+			// foo
+			post /foo (Foo) returns (Bar) // bar
+		}
+		`)
+		assert.Nil(t, err)
+		api := v.(*ast.ServiceApi)
+		assert.True(t, api.Equal(&ast.ServiceApi{
+			ServiceToken: ast.NewTextExpr("service"),
+			Name:         ast.NewTextExpr("foo-api"),
+			Lbrace:       ast.NewTextExpr("{"),
+			Rbrace:       ast.NewTextExpr("}"),
+			ServiceRoute: []*ast.ServiceRoute{
+				{
+					AtDoc: &ast.AtDoc{
+						AtDocToken: ast.NewTextExpr("@doc"),
+						Lp:         ast.NewTextExpr("("),
+						Rp:         ast.NewTextExpr(")"),
+						LineDoc:    ast.NewTextExpr(`"foo"`),
+					},
+					AtHandler: &ast.AtHandler{
+						AtHandlerToken: ast.NewTextExpr("@handler"),
+						Name:           ast.NewTextExpr("foo"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// foo"),
+						},
+						CommentExpr: ast.NewTextExpr("// bar"),
+					},
+					Route: &ast.Route{
+						Method: ast.NewTextExpr("post"),
+						Path:   ast.NewTextExpr("/foo"),
+						Req: &ast.Body{
+							Lp:   ast.NewTextExpr("("),
+							Rp:   ast.NewTextExpr(")"),
+							Name: &ast.Literal{Literal: ast.NewTextExpr("Foo")},
+						},
+						ReturnToken: ast.NewTextExpr("returns"),
+						Reply: &ast.Body{
+							Lp:   ast.NewTextExpr("("),
+							Rp:   ast.NewTextExpr(")"),
+							Name: &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+						},
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// foo"),
+						},
+						CommentExpr: ast.NewTextExpr("// bar"),
+					},
+				},
+			},
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(fn, `services foo-api{}`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `service foo-api{`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `service foo-api{
+		post /foo
+		}`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `service foo-api{
+		@handler foo
+		}`)
+		assert.Error(t, err)
+	})
+}
+
+func TestAtServer(t *testing.T) {
+	fn := func(p *api.ApiParserParser, v *ast.ApiVisitor) interface{} {
+		return p.AtServer().Accept(v)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fn, `
+		@server(
+			// foo
+			foo1: bar1 // bar
+			// foo
+			foo2: "bar2" // bar
+			/**foo*/
+			foo3: "foo
+			bar" /**bar*/		
+		)
+		`)
+		assert.Nil(t, err)
+		as := v.(*ast.AtServer)
+		assert.True(t, as.Equal(&ast.AtServer{
+			AtServerToken: ast.NewTextExpr("@server"),
+			Lp:            ast.NewTextExpr("("),
+			Rp:            ast.NewTextExpr(")"),
+			Kv: []*ast.KvExpr{
+				{
+					Key:   ast.NewTextExpr("foo1"),
+					Value: ast.NewTextExpr("bar1"),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// foo"),
+					},
+					CommentExpr: ast.NewTextExpr("// bar"),
+				},
+				{
+					Key:   ast.NewTextExpr("foo2"),
+					Value: ast.NewTextExpr(`"bar2"`),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// foo"),
+					},
+					CommentExpr: ast.NewTextExpr("// bar"),
+				},
+				{
+					Key: ast.NewTextExpr("foo3"),
+					Value: ast.NewTextExpr(`"foo
+			bar"`),
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("/**foo*/"),
+					},
+					CommentExpr: ast.NewTextExpr("/**bar*/"),
+				},
+			},
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(fn, `server (
+			foo:bar
+		)`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `@server ()`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `@server (
+			foo: bar
+		`)
+		assert.Error(t, err)
+	})
+}
+
+func TestServiceSpec(t *testing.T) {
+	fn := func(p *api.ApiParserParser, v *ast.ApiVisitor) interface{} {
+		return p.ServiceSpec().Accept(v)
+	}
+	t.Run("normal", func(t *testing.T) {
+		_, err := parser.Accept(fn, `
+		service foo-api{
+			@handler foo
+			post /foo returns ([]int)
+		}
+		`)
+		assert.Nil(t, err)
+
+		v, err := parser.Accept(fn, `
+		@server(
+			// foo
+			foo1: bar1 // bar
+			// foo
+			foo2: "bar2" // bar
+			/**foo*/
+			foo3: "foo
+			bar" /**bar*/		
+		)
+		service foo-api{
+			@doc("foo")
+			// foo/bar
+			// foo
+			@handler foo // bar
+			// foo/bar
+			// foo
+			post /foo (Foo) returns (Bar) // bar
+		}
+		`)
+		assert.Nil(t, err)
+		service := v.(*ast.Service)
+		assert.True(t, service.Equal(&ast.Service{
+			AtServer: &ast.AtServer{
+				AtServerToken: ast.NewTextExpr("@server"),
+				Lp:            ast.NewTextExpr("("),
+				Rp:            ast.NewTextExpr(")"),
+				Kv: []*ast.KvExpr{
+					{
+						Key:   ast.NewTextExpr("foo1"),
+						Value: ast.NewTextExpr("bar1"),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// foo"),
+						},
+						CommentExpr: ast.NewTextExpr("// bar"),
+					},
+					{
+						Key:   ast.NewTextExpr("foo2"),
+						Value: ast.NewTextExpr(`"bar2"`),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("// foo"),
+						},
+						CommentExpr: ast.NewTextExpr("// bar"),
+					},
+					{
+						Key: ast.NewTextExpr("foo3"),
+						Value: ast.NewTextExpr(`"foo
+			bar"`),
+						DocExpr: []ast.Expr{
+							ast.NewTextExpr("/**foo*/"),
+						},
+						CommentExpr: ast.NewTextExpr("/**bar*/"),
+					},
+				},
+			},
+			ServiceApi: &ast.ServiceApi{
+				ServiceToken: ast.NewTextExpr("service"),
+				Name:         ast.NewTextExpr("foo-api"),
+				Lbrace:       ast.NewTextExpr("{"),
+				Rbrace:       ast.NewTextExpr("}"),
+				ServiceRoute: []*ast.ServiceRoute{
+					{
+						AtDoc: &ast.AtDoc{
+							AtDocToken: ast.NewTextExpr("@doc"),
+							Lp:         ast.NewTextExpr("("),
+							Rp:         ast.NewTextExpr(")"),
+							LineDoc:    ast.NewTextExpr(`"foo"`),
+						},
+						AtHandler: &ast.AtHandler{
+							AtHandlerToken: ast.NewTextExpr("@handler"),
+							Name:           ast.NewTextExpr("foo"),
+							DocExpr: []ast.Expr{
+								ast.NewTextExpr("// foo"),
+							},
+							CommentExpr: ast.NewTextExpr("// bar"),
+						},
+						Route: &ast.Route{
+							Method: ast.NewTextExpr("post"),
+							Path:   ast.NewTextExpr("/foo"),
+							Req: &ast.Body{
+								Lp:   ast.NewTextExpr("("),
+								Rp:   ast.NewTextExpr(")"),
+								Name: &ast.Literal{Literal: ast.NewTextExpr("Foo")},
+							},
+							ReturnToken: ast.NewTextExpr("returns"),
+							Reply: &ast.Body{
+								Lp:   ast.NewTextExpr("("),
+								Rp:   ast.NewTextExpr(")"),
+								Name: &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+							},
+							DocExpr: []ast.Expr{
+								ast.NewTextExpr("// foo"),
+							},
+							CommentExpr: ast.NewTextExpr("// bar"),
+						},
+					},
+				},
+			},
+		}))
+
+		v, err = parser.Accept(fn, `
+		service foo-api{
+			@doc("foo")
+			// foo/bar
+			// foo
+			@handler foo // bar
+			// foo/bar
+			// foo
+			post /foo (Foo) returns (Bar) // bar
+		}
+		`)
+		assert.Nil(t, err)
+		service = v.(*ast.Service)
+		assert.True(t, service.Equal(&ast.Service{
+			ServiceApi: &ast.ServiceApi{
+				ServiceToken: ast.NewTextExpr("service"),
+				Name:         ast.NewTextExpr("foo-api"),
+				Lbrace:       ast.NewTextExpr("{"),
+				Rbrace:       ast.NewTextExpr("}"),
+				ServiceRoute: []*ast.ServiceRoute{
+					{
+						AtDoc: &ast.AtDoc{
+							AtDocToken: ast.NewTextExpr("@doc"),
+							Lp:         ast.NewTextExpr("("),
+							Rp:         ast.NewTextExpr(")"),
+							LineDoc:    ast.NewTextExpr(`"foo"`),
+						},
+						AtHandler: &ast.AtHandler{
+							AtHandlerToken: ast.NewTextExpr("@handler"),
+							Name:           ast.NewTextExpr("foo"),
+							DocExpr: []ast.Expr{
+								ast.NewTextExpr("// foo"),
+							},
+							CommentExpr: ast.NewTextExpr("// bar"),
+						},
+						Route: &ast.Route{
+							Method: ast.NewTextExpr("post"),
+							Path:   ast.NewTextExpr("/foo"),
+							Req: &ast.Body{
+								Lp:   ast.NewTextExpr("("),
+								Rp:   ast.NewTextExpr(")"),
+								Name: &ast.Literal{Literal: ast.NewTextExpr("Foo")},
+							},
+							ReturnToken: ast.NewTextExpr("returns"),
+							Reply: &ast.Body{
+								Lp:   ast.NewTextExpr("("),
+								Rp:   ast.NewTextExpr(")"),
+								Name: &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+							},
+							DocExpr: []ast.Expr{
+								ast.NewTextExpr("// foo"),
+							},
+							CommentExpr: ast.NewTextExpr("// bar"),
+						},
+					},
+				},
+			},
+		}))
+	})
+}

+ 75 - 0
tools/goctl/api/parser/g4/test/syntax_test.go

@@ -0,0 +1,75 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+var syntaxAccept = func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+	return p.SyntaxLit().Accept(visitor)
+}
+
+func TestSyntax(t *testing.T) {
+	t.Run("matched", func(t *testing.T) {
+		v, err := parser.Accept(syntaxAccept, `syntax = "v1"`)
+		assert.Nil(t, err)
+
+		syntax := v.(*ast.SyntaxExpr)
+		assert.True(t, syntax.Equal(&ast.SyntaxExpr{
+			Syntax:  ast.NewTextExpr("syntax"),
+			Assign:  ast.NewTextExpr("="),
+			Version: ast.NewTextExpr(`"v1"`),
+		}))
+	})
+
+	t.Run("expecting syntax", func(t *testing.T) {
+		_, err := parser.Accept(syntaxAccept, `= "v1"`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(syntaxAccept, `syn = "v1"`)
+		assert.Error(t, err)
+	})
+
+	t.Run("missing assign", func(t *testing.T) {
+		_, err := parser.Accept(syntaxAccept, `syntax  "v1"`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(syntaxAccept, `syntax + "v1"`)
+		assert.Error(t, err)
+	})
+
+	t.Run("mismatched version", func(t *testing.T) {
+		_, err := parser.Accept(syntaxAccept, `syntax="v0"`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(syntaxAccept, `syntax = "v1a"`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(syntaxAccept, `syntax = "vv1"`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(syntaxAccept, `syntax = "1"`)
+		assert.Error(t, err)
+	})
+
+	t.Run("with comment", func(t *testing.T) {
+		v, err := parser.Accept(syntaxAccept, `
+		// doc
+		syntax="v1" // line comment`)
+		assert.Nil(t, err)
+
+		syntax := v.(*ast.SyntaxExpr)
+		assert.True(t, syntax.Equal(&ast.SyntaxExpr{
+			Syntax:  ast.NewTextExpr("syntax"),
+			Assign:  ast.NewTextExpr("="),
+			Version: ast.NewTextExpr(`"v1"`),
+			DocExpr: []ast.Expr{
+				ast.NewTextExpr("// doc"),
+			},
+			CommentExpr: ast.NewTextExpr("// line comment"),
+		}))
+	})
+}

+ 467 - 0
tools/goctl/api/parser/g4/test/type_test.go

@@ -0,0 +1,467 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
+)
+
+var fieldAccept = func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+	return p.Field().Accept(visitor)
+}
+
+func TestField(t *testing.T) {
+	t.Run("anonymous", func(t *testing.T) {
+		v, err := parser.Accept(fieldAccept, `User`)
+		assert.Nil(t, err)
+		f := v.(*ast.TypeField)
+		assert.True(t, f.Equal(&ast.TypeField{
+			IsAnonymous: true,
+			DataType:    &ast.Literal{Literal: ast.NewTextExpr("User")},
+		}))
+
+		v, err = parser.Accept(fieldAccept, `*User`)
+		assert.Nil(t, err)
+		f = v.(*ast.TypeField)
+		assert.True(t, f.Equal(&ast.TypeField{
+			IsAnonymous: true,
+			DataType: &ast.Pointer{
+				PointerExpr: ast.NewTextExpr("*User"),
+				Star:        ast.NewTextExpr("*"),
+				Name:        ast.NewTextExpr("User"),
+			},
+		}))
+
+		v, err = parser.Accept(fieldAccept, `
+		// anonymous user
+		*User // pointer type`)
+		assert.Nil(t, err)
+		f = v.(*ast.TypeField)
+		assert.True(t, f.Equal(&ast.TypeField{
+			IsAnonymous: true,
+			DataType: &ast.Pointer{
+				PointerExpr: ast.NewTextExpr("*User"),
+				Star:        ast.NewTextExpr("*"),
+				Name:        ast.NewTextExpr("User"),
+			},
+			DocExpr: []ast.Expr{
+				ast.NewTextExpr("// anonymous user"),
+			},
+			CommentExpr: ast.NewTextExpr("// pointer type"),
+		}))
+
+		_, err = parser.Accept(fieldAccept, `interface`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fieldAccept, `map`)
+		assert.Error(t, err)
+	})
+
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fieldAccept, `User int`)
+		assert.Nil(t, err)
+		f := v.(*ast.TypeField)
+		assert.True(t, f.Equal(&ast.TypeField{
+			Name:     ast.NewTextExpr("User"),
+			DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
+		}))
+		v, err = parser.Accept(fieldAccept, `Foo Bar`)
+		assert.Nil(t, err)
+		f = v.(*ast.TypeField)
+		assert.True(t, f.Equal(&ast.TypeField{
+			Name:     ast.NewTextExpr("Foo"),
+			DataType: &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+		}))
+
+		v, err = parser.Accept(fieldAccept, `Foo map[int]Bar`)
+		assert.Nil(t, err)
+		f = v.(*ast.TypeField)
+		assert.True(t, f.Equal(&ast.TypeField{
+			Name: ast.NewTextExpr("Foo"),
+			DataType: &ast.Map{
+				MapExpr: ast.NewTextExpr("map[int]Bar"),
+				Map:     ast.NewTextExpr("map"),
+				LBrack:  ast.NewTextExpr("["),
+				RBrack:  ast.NewTextExpr("]"),
+				Key:     ast.NewTextExpr("int"),
+				Value:   &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+			},
+		}))
+	})
+}
+
+func TestDataType_ID(t *testing.T) {
+	dt := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.DataType().Accept(visitor)
+	}
+	t.Run("Struct", func(t *testing.T) {
+		v, err := parser.Accept(dt, `Foo`)
+		assert.Nil(t, err)
+		id := v.(ast.DataType)
+		assert.True(t, id.Equal(&ast.Literal{Literal: ast.NewTextExpr("Foo")}))
+	})
+
+	t.Run("basic", func(t *testing.T) {
+		v, err := parser.Accept(dt, `int`)
+		assert.Nil(t, err)
+		id := v.(ast.DataType)
+		assert.True(t, id.Equal(&ast.Literal{Literal: ast.NewTextExpr("int")}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(dt, `map`)
+		assert.Error(t, err)
+	})
+}
+
+func TestDataType_Map(t *testing.T) {
+	dt := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.MapType().Accept(visitor)
+	}
+	t.Run("basicKey", func(t *testing.T) {
+		v, err := parser.Accept(dt, `map[int]Bar`)
+		assert.Nil(t, err)
+		m := v.(ast.DataType)
+		assert.True(t, m.Equal(&ast.Map{
+			MapExpr: ast.NewTextExpr("map[int]Bar"),
+			Map:     ast.NewTextExpr("map"),
+			LBrack:  ast.NewTextExpr("["),
+			RBrack:  ast.NewTextExpr("]"),
+			Key:     ast.NewTextExpr("int"),
+			Value:   &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(dt, `map[var]Bar`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(dt, `map[*User]Bar`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(dt, `map[User]Bar`)
+		assert.Error(t, err)
+	})
+}
+
+func TestDataType_Array(t *testing.T) {
+	dt := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.ArrayType().Accept(visitor)
+	}
+	t.Run("basic", func(t *testing.T) {
+		v, err := parser.Accept(dt, `[]int`)
+		assert.Nil(t, err)
+		array := v.(ast.DataType)
+		assert.True(t, array.Equal(&ast.Array{
+			ArrayExpr: ast.NewTextExpr("[]int"),
+			LBrack:    ast.NewTextExpr("["),
+			RBrack:    ast.NewTextExpr("]"),
+			Literal:   &ast.Literal{Literal: ast.NewTextExpr("int")},
+		}))
+	})
+
+	t.Run("pointer", func(t *testing.T) {
+		v, err := parser.Accept(dt, `[]*User`)
+		assert.Nil(t, err)
+		array := v.(ast.DataType)
+		assert.True(t, array.Equal(&ast.Array{
+			ArrayExpr: ast.NewTextExpr("[]*User"),
+			LBrack:    ast.NewTextExpr("["),
+			RBrack:    ast.NewTextExpr("]"),
+			Literal: &ast.Pointer{
+				PointerExpr: ast.NewTextExpr("*User"),
+				Star:        ast.NewTextExpr("*"),
+				Name:        ast.NewTextExpr("User"),
+			},
+		}))
+	})
+
+	t.Run("interface{}", func(t *testing.T) {
+		v, err := parser.Accept(dt, `[]interface{}`)
+		assert.Nil(t, err)
+		array := v.(ast.DataType)
+		assert.True(t, array.Equal(&ast.Array{
+			ArrayExpr: ast.NewTextExpr("[]interface{}"),
+			LBrack:    ast.NewTextExpr("["),
+			RBrack:    ast.NewTextExpr("]"),
+			Literal:   &ast.Interface{Literal: ast.NewTextExpr("interface{}")},
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(dt, `[]var`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(dt, `[]interface`)
+		assert.Error(t, err)
+	})
+}
+
+func TestDataType_Interface(t *testing.T) {
+	dt := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.DataType().Accept(visitor)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(dt, `interface{}`)
+		assert.Nil(t, err)
+		inter := v.(ast.DataType)
+		assert.True(t, inter.Equal(&ast.Interface{Literal: ast.NewTextExpr("interface{}")}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(dt, `interface`)
+		assert.Error(t, err)
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(dt, `interface{`)
+		assert.Error(t, err)
+	})
+}
+
+func TestDataType_Time(t *testing.T) {
+	dt := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.DataType().Accept(visitor)
+	}
+	t.Run("normal", func(t *testing.T) {
+		_, err := parser.Accept(dt, `time.Time`)
+		assert.Error(t, err)
+	})
+}
+
+func TestDataType_Pointer(t *testing.T) {
+	dt := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.PointerType().Accept(visitor)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(dt, `*int`)
+		assert.Nil(t, err)
+		assert.True(t, v.(ast.DataType).Equal(&ast.Pointer{
+			PointerExpr: ast.NewTextExpr("*int"),
+			Star:        ast.NewTextExpr("*"),
+			Name:        ast.NewTextExpr("int"),
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(dt, `int`)
+		assert.Error(t, err)
+	})
+}
+
+func TestAlias(t *testing.T) {
+	fn := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.TypeAlias().Accept(visitor)
+	}
+	t.Run("normal", func(t *testing.T) {
+		_, err := parser.Accept(fn, `Foo int`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `Foo=int`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `
+		Foo int // comment`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `
+		Foo int /**comment*/`)
+		assert.Error(t, err)
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(fn, `Foo var`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `Foo 2`)
+		assert.Error(t, err)
+	})
+}
+
+func TestTypeStruct(t *testing.T) {
+	fn := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.TypeStruct().Accept(visitor)
+	}
+
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fn, "Foo {\n\t\t\tFoo string\n\t\t\tBar int `json:\"bar\"``\n\t\t}")
+		assert.Nil(t, err)
+		s := v.(*ast.TypeStruct)
+		assert.True(t, s.Equal(&ast.TypeStruct{
+			Name:   ast.NewTextExpr("Foo"),
+			LBrace: ast.NewTextExpr("{"),
+			RBrace: ast.NewTextExpr("}"),
+			Fields: []*ast.TypeField{
+				{
+					Name:     ast.NewTextExpr("Foo"),
+					DataType: &ast.Literal{Literal: ast.NewTextExpr("string")},
+				},
+				{
+					Name:     ast.NewTextExpr("Bar"),
+					DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
+					Tag:      ast.NewTextExpr("`json:\"bar\"`"),
+				},
+			},
+		}))
+
+		v, err = parser.Accept(fn, "Foo struct{\n\t\t\tFoo string\n\t\t\tBar int `json:\"bar\"``\n\t\t}")
+		assert.Nil(t, err)
+		s = v.(*ast.TypeStruct)
+		assert.True(t, s.Equal(&ast.TypeStruct{
+			Name:   ast.NewTextExpr("Foo"),
+			LBrace: ast.NewTextExpr("{"),
+			RBrace: ast.NewTextExpr("}"),
+			Struct: ast.NewTextExpr("struct"),
+			Fields: []*ast.TypeField{
+				{
+					Name:     ast.NewTextExpr("Foo"),
+					DataType: &ast.Literal{Literal: ast.NewTextExpr("string")},
+				},
+				{
+					Name:     ast.NewTextExpr("Bar"),
+					DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
+					Tag:      ast.NewTextExpr("`json:\"bar\"`"),
+				},
+			},
+		}))
+	})
+}
+
+func TestTypeBlock(t *testing.T) {
+	fn := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.TypeBlock().Accept(visitor)
+	}
+	t.Run("normal", func(t *testing.T) {
+		v, err := parser.Accept(fn, `type(
+			// doc
+			Foo int
+		)`)
+		assert.Error(t, err)
+
+		v, err = parser.Accept(fn, `type (
+			// doc
+			Foo {
+				Bar int
+			}
+		)`)
+		assert.Nil(t, err)
+		st := v.([]ast.TypeExpr)
+		assert.True(t, st[0].Equal(&ast.TypeStruct{
+			Name:   ast.NewTextExpr("Foo"),
+			LBrace: ast.NewTextExpr("{"),
+			RBrace: ast.NewTextExpr("}"),
+			DocExpr: []ast.Expr{
+				ast.NewTextExpr("// doc"),
+			},
+			Fields: []*ast.TypeField{
+				{
+					Name:     ast.NewTextExpr("Bar"),
+					DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
+				},
+			},
+		}))
+	})
+}
+
+func TestTypeLit(t *testing.T) {
+	fn := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.TypeLit().Accept(visitor)
+	}
+	t.Run("normal", func(t *testing.T) {
+		_, err := parser.Accept(fn, `type Foo int`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `type Foo = int`)
+		assert.Error(t, err)
+
+		_, err = parser.Accept(fn, `
+		// doc
+		type Foo = int // comment`)
+		assert.Error(t, err)
+
+		v, err := parser.Accept(fn, `
+		// doc
+		type Foo {// comment
+			Bar int
+		}`)
+		assert.Nil(t, err)
+		st := v.(*ast.TypeStruct)
+		assert.True(t, st.Equal(&ast.TypeStruct{
+			Name: ast.NewTextExpr("Foo"),
+			Fields: []*ast.TypeField{
+				{
+					Name:     ast.NewTextExpr("Bar"),
+					DataType: &ast.Literal{Literal: ast.NewTextExpr("int")},
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// comment"),
+					},
+				},
+			},
+			DocExpr: []ast.Expr{
+				ast.NewTextExpr("// doc"),
+			},
+		}))
+
+		v, err = parser.Accept(fn, `
+		// doc
+		type Foo {// comment
+			Bar
+		}`)
+		assert.Nil(t, err)
+		st = v.(*ast.TypeStruct)
+		assert.True(t, st.Equal(&ast.TypeStruct{
+			Name: ast.NewTextExpr("Foo"),
+			Fields: []*ast.TypeField{
+				{
+					IsAnonymous: true,
+					DataType:    &ast.Literal{Literal: ast.NewTextExpr("Bar")},
+					DocExpr: []ast.Expr{
+						ast.NewTextExpr("// comment"),
+					},
+				},
+			},
+			DocExpr: []ast.Expr{
+				ast.NewTextExpr("// doc"),
+			},
+		}))
+	})
+
+	t.Run("wrong", func(t *testing.T) {
+		_, err := parser.Accept(fn, `type Foo`)
+		assert.Error(t, err)
+	})
+}
+
+func TestTypeUnExported(t *testing.T) {
+	fn := func(p *api.ApiParserParser, visitor *ast.ApiVisitor) interface{} {
+		return p.TypeSpec().Accept(visitor)
+	}
+
+	t.Run("type", func(t *testing.T) {
+		_, err := parser.Accept(fn, `type foo {}`)
+		assert.Nil(t, err)
+	})
+
+	t.Run("field", func(t *testing.T) {
+		_, err := parser.Accept(fn, `type Foo {
+			name int
+		}`)
+		assert.Nil(t, err)
+
+		_, err = parser.Accept(fn, `type Foo {
+			Name int
+		}`)
+		assert.Nil(t, err)
+	})
+
+	t.Run("filedDataType", func(t *testing.T) {
+		_, err := parser.Accept(fn, `type Foo {
+			Foo *foo
+			Bar []bar
+			FooBar map[int]fooBar
+		}`)
+		assert.Nil(t, err)
+	})
+}

+ 0 - 62
tools/goctl/api/parser/infostate.go

@@ -1,62 +0,0 @@
-package parser
-
-import (
-	"fmt"
-	"strings"
-
-	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-)
-
-const (
-	titleTag   = "title"
-	descTag    = "desc"
-	versionTag = "version"
-	authorTag  = "author"
-	emailTag   = "email"
-)
-
-type infoState struct {
-	*baseState
-	innerState int
-}
-
-func newInfoState(st *baseState) state {
-	return &infoState{
-		baseState:  st,
-		innerState: startState,
-	}
-}
-
-func (s *infoState) process(api *spec.ApiSpec) (state, error) {
-	attrs, err := s.parseProperties()
-	if err != nil {
-		return nil, err
-	}
-
-	if err := s.writeInfo(api, attrs); err != nil {
-		return nil, err
-	}
-
-	return newRootState(s.r, s.lineNumber), nil
-}
-
-func (s *infoState) writeInfo(api *spec.ApiSpec, attrs map[string]string) error {
-	for k, v := range attrs {
-		switch k {
-		case titleTag:
-			api.Info.Title = strings.TrimSpace(v)
-		case descTag:
-			api.Info.Desc = strings.TrimSpace(v)
-		case versionTag:
-			api.Info.Version = strings.TrimSpace(v)
-		case authorTag:
-			api.Info.Author = strings.TrimSpace(v)
-		case emailTag:
-			api.Info.Email = strings.TrimSpace(v)
-		default:
-			return fmt.Errorf("unknown directive %q in %q section", k, infoDirective)
-		}
-	}
-
-	return nil
-}

+ 249 - 60
tools/goctl/api/parser/parser.go

@@ -1,103 +1,292 @@
 package parser
 
 import (
-	"bufio"
-	"bytes"
 	"errors"
 	"fmt"
-	"io"
-	"io/ioutil"
 	"path/filepath"
-	"strings"
+	"unicode"
 
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/ast"
+	"github.com/tal-tech/go-zero/tools/goctl/api/parser/g4/gen/api"
 	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-	"github.com/tal-tech/go-zero/tools/goctl/util"
 )
 
-type Parser struct {
-	r   *bufio.Reader
-	api *ApiStruct
+type parser struct {
+	ast  *ast.Api
+	spec *spec.ApiSpec
 }
 
-func NewParser(filename string) (*Parser, error) {
-	apiAbsPath, err := filepath.Abs(filename)
+func Parse(filename string) (*spec.ApiSpec, error) {
+	astParser := ast.NewParser(ast.WithParserPrefix(filepath.Base(filename)))
+	ast, err := astParser.Parse(filename)
 	if err != nil {
 		return nil, err
 	}
 
-	api, err := ioutil.ReadFile(filename)
+	spec := new(spec.ApiSpec)
+	p := parser{ast: ast, spec: spec}
+	err = p.convert2Spec()
 	if err != nil {
 		return nil, err
 	}
 
-	apiStruct, err := ParseApi(string(api))
+	return spec, nil
+}
+
+func ParseContent(content string) (*spec.ApiSpec, error) {
+	astParser := ast.NewParser()
+	ast, err := astParser.ParseContent(content)
 	if err != nil {
 		return nil, err
 	}
 
-	for _, item := range strings.Split(apiStruct.Imports, "\n") {
-		importLine := strings.TrimSpace(item)
-		if len(importLine) > 0 {
-			item := strings.TrimPrefix(importLine, "import")
-			item = strings.TrimSpace(item)
-			item = strings.TrimPrefix(item, `"`)
-			item = strings.TrimSuffix(item, `"`)
-			var path = item
-			if !util.FileExists(item) {
-				path = filepath.Join(filepath.Dir(apiAbsPath), item)
-			}
-			content, err := ioutil.ReadFile(path)
-			if err != nil {
-				return nil, errors.New("import api file not exist: " + item)
-			}
+	spec := new(spec.ApiSpec)
+	p := parser{ast: ast, spec: spec}
+	err = p.convert2Spec()
+	if err != nil {
+		return nil, err
+	}
 
-			importStruct, err := ParseApi(string(content))
-			if err != nil {
-				return nil, err
+	return spec, nil
+}
+
+func (p parser) convert2Spec() error {
+	p.fillInfo()
+	p.fillSyntax()
+	p.fillImport()
+	err := p.fillTypes()
+	if err != nil {
+		return err
+	}
+	return p.fillService()
+}
+
+func (p parser) fillInfo() {
+	var properties = make(map[string]string, 0)
+	if p.ast.Info != nil {
+		p.spec.Info = spec.Info{}
+		for _, kv := range p.ast.Info.Kvs {
+			properties[kv.Key.Text()] = kv.Value.Text()
+		}
+	}
+	p.spec.Info.Properties = properties
+}
+
+func (p parser) fillSyntax() {
+	if p.ast.Syntax != nil {
+		p.spec.Syntax = spec.ApiSyntax{Version: p.ast.Syntax.Version.Text()}
+	}
+}
+
+func (p parser) fillImport() {
+	if len(p.ast.Import) > 0 {
+		for _, item := range p.ast.Import {
+			p.spec.Imports = append(p.spec.Imports, spec.Import{Value: item.Value.Text()})
+		}
+	}
+}
+
+func (p parser) fillTypes() error {
+	for _, item := range p.ast.Type {
+		switch v := (item).(type) {
+		case *ast.TypeStruct:
+			var members []spec.Member
+			for _, item := range v.Fields {
+				members = append(members, p.fieldToMember(item))
 			}
+			p.spec.Types = append(p.spec.Types, spec.DefineStruct{
+				RawName: v.Name.Text(),
+				Members: members,
+				Docs:    p.stringExprs(v.Doc()),
+			})
+		default:
+			return errors.New(fmt.Sprintf("unknown type %+v", v))
+		}
+	}
 
-			if len(importStruct.Imports) > 0 {
-				return nil, errors.New("import api should not import another api file recursive")
+	for _, item := range p.spec.Types {
+		switch v := (item).(type) {
+		case spec.DefineStruct:
+			for _, member := range v.Members {
+				switch v := member.Type.(type) {
+				case spec.DefineStruct:
+					tp, err := p.findDefinedType(v.RawName)
+					if err != nil {
+						return err
+					} else {
+						member.Type = *tp
+					}
+				}
 			}
+		default:
+			return errors.New(fmt.Sprintf("unknown type %+v", v))
+		}
+	}
+	return nil
+}
 
-			apiStruct.Type += "\n" + importStruct.Type
-			apiStruct.Service += "\n" + importStruct.Service
+func (p parser) findDefinedType(name string) (*spec.Type, error) {
+	for _, item := range p.spec.Types {
+		if _, ok := item.(spec.DefineStruct); ok {
+			if item.Name() == name {
+				return &item, nil
+			}
 		}
 	}
+	return nil, errors.New(fmt.Sprintf("type %s not defined", name))
+}
 
-	if len(strings.TrimSpace(apiStruct.Service)) == 0 {
-		return nil, errors.New("api has no service defined")
+func (p parser) fieldToMember(field *ast.TypeField) spec.Member {
+	var name = ""
+	var tag = ""
+	if !field.IsAnonymous {
+		name = field.Name.Text()
+		tag = field.Tag.Text()
 	}
+	return spec.Member{
+		Name:     name,
+		Type:     p.astTypeToSpec(field.DataType),
+		Tag:      tag,
+		Comment:  p.commentExprs(field.Comment()),
+		Docs:     p.stringExprs(field.Doc()),
+		IsInline: field.IsAnonymous,
+	}
+}
 
-	var buffer = new(bytes.Buffer)
-	buffer.WriteString(apiStruct.Service)
-	return &Parser{
-		r:   bufio.NewReader(buffer),
-		api: apiStruct,
-	}, nil
+func (p parser) astTypeToSpec(in ast.DataType) spec.Type {
+	switch v := (in).(type) {
+	case *ast.Literal:
+		raw := v.Literal.Text()
+		if api.IsBasicType(raw) {
+			return spec.PrimitiveType{RawName: raw}
+		} else {
+			return spec.DefineStruct{RawName: raw}
+		}
+	case *ast.Interface:
+		return spec.InterfaceType{RawName: v.Literal.Text()}
+	case *ast.Map:
+		return spec.MapType{RawName: v.MapExpr.Text(), Key: v.Key.Text(), Value: p.astTypeToSpec(v.Value)}
+	case *ast.Array:
+		return spec.ArrayType{RawName: v.ArrayExpr.Text(), Value: p.astTypeToSpec(v.Literal)}
+	case *ast.Pointer:
+		raw := v.Name.Text()
+		if api.IsBasicType(raw) {
+			return spec.PointerType{RawName: v.PointerExpr.Text(), Type: spec.PrimitiveType{RawName: raw}}
+		} else {
+			return spec.PointerType{RawName: v.PointerExpr.Text(), Type: spec.DefineStruct{RawName: raw}}
+		}
+	}
+	panic(fmt.Sprintf("unspported type %+v", in))
 }
 
-func (p *Parser) Parse() (api *spec.ApiSpec, err error) {
-	api = new(spec.ApiSpec)
-	var sp = StructParser{Src: p.api.Type}
-	types, err := sp.Parse()
-	if err != nil {
-		return nil, err
+func (p parser) stringExprs(docs []ast.Expr) []string {
+	var result []string
+	for _, item := range docs {
+		result = append(result, item.Text())
 	}
+	return result
+}
+
+func (p parser) commentExprs(comment ast.Expr) string {
+	if comment == nil {
+		return ""
+	}
+	return comment.Text()
+}
+
+func (p parser) fillService() error {
+	var groups []spec.Group
+	for _, item := range p.ast.Service {
+		var group spec.Group
+		if item.AtServer != nil {
+			var properties = make(map[string]string, 0)
+			for _, kv := range item.AtServer.Kv {
+				properties[kv.Key.Text()] = kv.Value.Text()
+			}
+			group.Annotation.Properties = properties
+		}
+
+		for _, astRoute := range item.ServiceApi.ServiceRoute {
+			route := spec.Route{
+				Annotation: spec.Annotation{},
+				Method:     astRoute.Route.Method.Text(),
+				Path:       astRoute.Route.Path.Text(),
+			}
+			if astRoute.AtHandler != nil {
+				route.Handler = astRoute.AtHandler.Name.Text()
+			}
+
+			if astRoute.AtServer != nil {
+				var properties = make(map[string]string, 0)
+				for _, kv := range astRoute.AtServer.Kv {
+					properties[kv.Key.Text()] = kv.Value.Text()
+				}
+				route.Annotation.Properties = properties
+				if len(route.Handler) == 0 {
+					route.Handler = properties["handler"]
+				}
+				if len(route.Handler) == 0 {
+					return fmt.Errorf("missing handler annotation for %q", route.Path)
+				}
+
+				for _, char := range route.Handler {
+					if !unicode.IsDigit(char) && !unicode.IsLetter(char) {
+						return errors.New(fmt.Sprintf("route [%s] handler [%s] invalid, handler name should only contains letter or digit",
+							route.Path, route.Handler))
+					}
+				}
+			}
+
+			if astRoute.Route.Req != nil {
+				route.RequestType = p.astTypeToSpec(astRoute.Route.Req.Name)
+			}
+			if astRoute.Route.Reply != nil {
+				route.ResponseType = p.astTypeToSpec(astRoute.Route.Reply.Name)
+			}
+
+			err := p.fillRouteType(&route)
+			if err != nil {
+				return err
+			}
+
+			group.Routes = append(group.Routes, route)
 
-	api.Types = types
-	var lineNumber = p.api.serviceBeginLine
-	st := newRootState(p.r, &lineNumber)
-	for {
-		st, err = st.process(api)
-		if err == io.EOF {
-			return api, p.validate(api)
+			name := item.ServiceApi.Name.Text()
+			if len(p.spec.Service.Name) > 0 && p.spec.Service.Name != name {
+				return errors.New(fmt.Sprintf("mulit service name defined %s and %s", name, p.spec.Service.Name))
+			}
+			p.spec.Service.Name = name
 		}
-		if err != nil {
-			return nil, fmt.Errorf("near line: %d, %s", lineNumber, err.Error())
+		groups = append(groups, group)
+	}
+	p.spec.Service.Groups = groups
+
+	return nil
+}
+
+func (p parser) fillRouteType(route *spec.Route) error {
+	if route.RequestType != nil {
+		switch route.RequestType.(type) {
+		case spec.DefineStruct:
+			tp, err := p.findDefinedType(route.RequestType.Name())
+			if err != nil {
+				return err
+			}
+
+			route.RequestType = *tp
 		}
-		if st == nil {
-			return api, p.validate(api)
+	}
+
+	if route.ResponseType != nil {
+		switch route.ResponseType.(type) {
+		case spec.DefineStruct:
+			tp, err := p.findDefinedType(route.ResponseType.Name())
+			if err != nil {
+				return err
+			}
+
+			route.ResponseType = *tp
 		}
 	}
+	return nil
 }

+ 682 - 0
tools/goctl/api/parser/readme.md

@@ -0,0 +1,682 @@
+# Api语法描述
+
+## api示例
+
+``` golang
+/**
+ * api语法示例及语法说明
+ */
+
+// api语法版本
+syntax = "v1"
+
+// import literal
+import "foo.api"
+
+// import group
+import (
+    "bar.api"
+    "foo/bar.api"
+)
+info(
+    author: "songmeizi"
+    date:   "2020-01-08"
+    desc:   "api语法示例及语法说明"
+)
+
+// type literal
+
+type Foo{
+    Foo int `json:"foo"`
+}
+
+// type group
+
+type(
+    Bar{
+        Bar int `json:"bar"`
+    }
+)
+
+// service block
+@server(
+    jwt:   Auth
+    group: foo
+)
+service foo-api{
+    @doc "foo"
+    @handler foo
+    post /foo (Foo) returns (Bar)
+}
+```
+
+## api语法结构
+
+* syntax语法声明
+* import语法块
+* info语法块
+* type语法块
+* service语法块
+* 隐藏通道
+
+> ### 温馨提示️
+> 在以上语法结构中,各个语法块从语法上来说,按照语法块为单位,可以在.api文件中任意位置声明,
+> 但是为了提高阅读效率,我们建议按照以上顺序进行声明,因为在将来可能会通过严格模式来控制语法块的顺序。
+
+### syntax语法声明
+
+syntax是新加入的语法结构,该语法的引入可以解决:
+
+* 快速针对api版本定位存在问题的语法结构
+* 针对版本做语法解析
+* 防止api语法大版本升级导致前后不能向前兼容
+
+> ### 警告 ⚠️
+> 被import的api必须要和main api的syntax版本一致。
+
+**语法定义**
+
+``` antlrv4
+'syntax'={checkVersion(p)}STRING
+```
+
+**语法说明**
+> syntax:固定token,标志一个syntax语法结构的开始
+>
+> checkVersion:自定义go方法,检测`STRING`是否为一个合法的版本号,目前检测逻辑为,STRING必须是满足`(?m)"v[1-9][0-9]*"`正则。
+>
+> STRING:一串英文双引号包裹的字符串,如"v1"
+>
+> 一个api语法文件只能有0或者1个syntax语法声明,如果没有syntax,则默认为v1版本
+>
+
+
+**正确语法示例** ✅
+
+eg1:不规范写法
+
+``` api
+syntax="v1"
+```
+
+eg2:规范写法(推荐)
+
+``` api
+syntax = "v2"
+```
+
+**错误语法示例** ❌
+
+eg1:
+
+``` api
+syntax = "v0"
+```
+
+eg2:
+
+``` api
+syntax = v1
+```
+
+eg3:
+
+``` api
+syntax = "V1"
+```
+
+## import语法块
+
+随着业务规模增大,api中定义的结构体和服务越来越多,所有的语法描述均为一个api文件,这是多么糟糕的一个问题, 其会大大增加了阅读难度和维护难度,import语法块可以帮助我们解决这个问题,通过拆分api文件,
+不同的api文件按照一定规则声明,可以降低阅读难度和维护难度。
+
+> ### 警告 ⚠️
+> 这里import不像golang那样包含package声明,仅仅是一个文件路径的引入,最终解析后会把所有的声明都汇聚到一个spec.Spec中。
+> 不能import多个相同路径,否则会解析错误。
+
+**语法定义**
+
+``` antlrv4
+'import' {checkImportValue(p)}STRING  
+|'import' '(' ({checkImportValue(p)}STRING)+ ')'
+```
+
+**语法说明**
+> import:固定token,标志一个import语法的开始
+>
+> checkImportValue:自定义go方法,检测`STRING`是否为一个合法的文件路径,目前检测逻辑为,STRING必须是满足`(?m)"(/?[a-zA-Z0-9_#-])+\.api"`正则。
+>
+> STRING:一串英文双引号包裹的字符串,如"foo.api"
+>
+
+
+**正确语法示例** ✅
+
+eg:
+
+``` api
+import "foo.api"
+import "foo/bar.api"
+
+import(
+    "bar.api"
+    "foo/bar/foo.api"
+)
+```
+
+**错误语法示例** ❌
+
+eg:
+
+``` api
+import foo.api
+import "foo.txt"
+import (
+    bar.api
+    bar.api
+)
+```
+
+## info语法块
+
+info语法块是一个包含了多个键值对的语法体,其作用相当于一个api服务的描述,解析器会将其映射到spec.Spec中, 以备用于翻译成其他语言(golang、java等)
+时需要携带的meta元素。如果仅仅是对当前api的一个说明,而不考虑其翻译 时传递到其他语言,则使用简单的多行注释或者java风格的文档注释即可,关于注释说明请参考下文的 **隐藏通道**。
+
+> ### 警告 ⚠️
+> 不能使用重复的key,每个api文件只能有0或者1个info语法块
+
+**语法定义**
+
+``` antlrv4
+'info' '(' (ID {checkKeyValue(p)}VALUE)+ ')'
+```
+
+**语法说明**
+> info:固定token,标志一个info语法块的开始
+>
+> checkKeyValue:自定义go方法,检测`VALUE`是否为一个合法值。
+>
+> VALUE:key对应的值,可以为单行的除'\r','\n','/'后的任意字符,多行请以""包裹,不过强烈建议所有都以""包裹
+>
+
+**正确语法示例** ✅
+
+eg1:不规范写法
+
+``` api
+info(
+foo: foo value
+bar:"bar value"
+    desc:"long long long long
+long long text"
+)
+```
+
+eg2:规范写法(推荐)
+
+``` api
+info(
+    foo: "foo value"
+    bar: "bar value"
+    desc: "long long long long long long text"
+)
+```
+
+**错误语法示例** ❌
+
+eg1:没有key-value内容
+
+``` api
+info()
+```
+
+eg2:不包含冒号
+
+``` api
+info(
+    foo value
+)
+```
+
+eg3:key-value没有换行
+
+``` api
+info(foo:"value")
+```
+
+eg4:没有key
+
+``` api
+info(
+    : "value"
+)
+```
+
+eg5:非法的key
+
+``` api
+info(
+    12: "value"
+)
+```
+
+eg6:移除旧版本多行语法
+
+``` api
+info(
+    foo: >
+    some text
+    <
+)
+```
+
+## type语法块
+
+在api服务中,我们需要用到一个结构体(类)来作为请求体,响应体的载体,因此我们需要声明一些结构体来完成这件事情, type语法块由golang的type演变而来,当然也保留着一些golang type的特性,沿用golang特性有:
+
+* 保留了golang内置数据类型`bool`,`int`,`int8`,`int16`,`int32`,`int64`,`uint`,`uint8`,`uint16`,`uint32`,`uint64`,`uintptr`
+  ,`float32`,`float64`,`complex64`,`complex128`,`string`,`byte`,`rune`,
+* 兼容golang struct风格声明
+* 保留golang关键字
+
+> ### 警告 ⚠️
+> * 不支持alias
+> * 不支持time.Time数据类型
+> * 结构体名称、字段名称、不能为golang关键字
+
+**语法定义**
+> 由于其和golang相似,因此不做详细说明,具体语法定义请在[ApiParser.g4](g4/ApiParser.g4)中查看typeSpec定义。
+
+**语法说明**
+
+> 参考golang写法
+
+**正确语法示例** ✅
+
+eg1:不规范写法
+
+``` api
+type Foo struct{
+    Id int `path:"id"` // ①
+    Foo int `json:"foo"`
+}
+
+type Bar struct{
+    // 非导出型字段
+    bar int `form:"bar"`
+}
+
+type(
+    // 非导出型结构体
+    fooBar struct{
+        FooBar int
+    }
+)
+```
+
+eg2:规范写法(推荐)
+
+``` api
+type Foo{
+    Id int `path:"id"`
+    Foo int `json:"foo"`
+}
+
+type Bar{
+    Bar int `form:"bar"`
+}
+
+type(
+    FooBar{
+        FooBar int
+    }
+)
+```
+
+**错误语法示例** ❌
+
+eg
+
+``` api
+type Gender int // 不支持
+
+// 非struct token
+type Foo structure{ 
+  CreateTime time.Time // 不支持time.Time
+}
+
+// golang关键字 var
+type var{} 
+
+type Foo{
+  // golang关键字 interface
+  Foo interface 
+}
+
+
+type Foo{
+  foo int 
+  // map key必须要golang内置数据类型
+  m map[Bar]string
+}
+```
+
+**① tag说明**
+> tag定义和golang中json tag语法一样,除了json tag外,go-zero还提供了另外一些tag来实现对字段的描述,
+> 详情见下表。
+
+* tag表
+
+  |tag key |描述 |提供方 |有效范围 |示例 |
+    |:--- |:--- |:--- |:--- |:--- |
+  |json|json序列化tag|golang|request、response|`json:"fooo"`|
+  |path|路由path,如`/foo/:id`|go-zero|request|`path:"id"`|
+  |form|标志请求体是一个form(POST方法时)或者一个query(GET方法时`/search?name=keyword`)|go-zero|request|`form:"name"`|
+
+* tag修饰符
+  > 常见参数校验描述
+
+  |tag key |描述 |提供方 |有效范围 |示例 |
+    |:--- |:--- |:--- |:--- |:--- |
+  |optional|定义当前字段为可选参数|go-zero|request|`json:"name,optional"`|
+  |options|定义当前字段的枚举值,多个以竖线②隔开|go-zero|request|`json:"gender,options=male"`|
+  |default|定义当前字段默认值|go-zero|request|`json:"gender,default=male"`|
+  |range|定义当前字段数值范围|go-zero|request|`json:"age,range=[0:120]"`|
+
+  ② 竖线:|
+  > ### 温馨提示
+  > tag修饰符需要在tag value后以引文逗号,隔开
+
+## service语法块
+
+service语法块用于定义api服务,包含服务名称,服务metadata,中间件声明,路由,handler等。
+
+> ### 警告 ⚠️
+> * main api和被import的api服务名称必须一致,不能出现服务名称歧义。
+> * handler名称不能重复
+> * 路由(请求方法+请求path)名称不能重复
+> * 请求体必须声明为普通(非指针)struct,响应体做了一些向前兼容处理,详请见下文说明
+>
+
+**语法定义**
+
+``` antlrv4
+serviceSpec:    atServer? serviceApi;
+atServer:       '@server' lp='(' kvLit+ rp=')';
+serviceApi:     {match(p,"service")}serviceToken=ID serviceName lbrace='{' serviceRoute* rbrace='}';
+serviceRoute:   atDoc? (atServer|atHandler) route;
+atDoc:          '@doc' lp='('? ((kvLit+)|STRING) rp=')'?;
+atHandler:      '@handler' ID;
+route:          {checkHttpMethod(p)}httpMethod=ID path request=body? returnToken=ID? response=replybody?;
+body:           lp='(' (ID)? rp=')';
+replybody:      lp='(' dataType? rp=')';
+// kv
+kvLit:          key=ID {checkKeyValue(p)}value=LINE_VALUE;
+
+serviceName:    (ID '-'?)+;
+path:           (('/' (ID ('-' ID)*))|('/:' (ID ('-' ID)?)))+;
+```
+
+**语法说明**
+
+> serviceSpec:包含了一个可选语法块`atServer`和`serviceApi`语法块,其遵循序列模式(编写service必须要按照顺序,否则会解析出错)
+>
+> atServer: 可选语法块,定义key-value结构的server metadata,'@server'表示这一个server语法块的开始,其可以用于描述serviceApi或者route语法块,其用于描述不同语法块时有一些特殊关键key
+> 需要值得注意,见 **atServer关键key描述说明**。
+>
+> serviceApi:包含了1到多个`serviceRoute`语法块
+>
+> serviceRoute:按照序列模式包含了`atDoc`,handler和`route`
+>
+> atDoc:可选语法块,一个路由的key-value描述,其在解析后会传递到spec.Spec结构体,如果不关心传递到spec.Spec,
+> 推荐用单行注释替代。
+>
+> handler:是对路由的handler层描述,可以通过atServer指定`handler` key来指定handler名称,
+> 也可以直接用atHandler语法块来定义handler名称
+>
+> atHandler:'@handler' 固定token,后接一个遵循正则`[_a-zA-Z][a-zA-Z_-]*`)的值,用于声明一个handler名称
+>
+> route:路由,有`httpMethod`、`path`、可选`request`、可选`response`组成,`httpMethod`是必须是小写。
+>
+> body:api请求体语法定义,必须要由()包裹的可选的ID值
+>
+> replyBody:api响应体语法定义,必须由()包裹的struct、~~array(向前兼容处理,后续可能会废弃,强烈推荐以struct包裹,不要直接用array作为响应体)~~
+>
+> kvLit: 同info key-value
+>
+> serviceName: 可以有多个'-'join的ID值
+>
+> path:api请求路径,必须以'/'或者'/:'开头,切不能以'/'结尾,中间可包含ID或者多个以'-'join的ID字符串
+
+**atServer关键key描述说明**
+
+修饰service时
+
+|key|描述|示例|
+|:---|:---|:---|
+|jwt|声明当前service下所有路由需要jwt鉴权,且会自动生成包含jwt逻辑的代码|`jwt: Auth`|
+|group|声明当前service或者路由文件分组|`group: login`|
+|middleware|声明当前service需要开启中间件|`middleware: AuthMiddleware`|
+
+修饰route时
+
+|key|描述|示例|
+|:---|:---|:---|
+|handler|声明一个handler|-|
+
+**正确语法示例** ✅
+
+eg1:不规范写法
+
+``` api
+@server(
+  jwt: Auth
+  group: foo
+  middleware: AuthMiddleware
+)
+service foo-api{
+  @doc(
+    summary: foo
+  )
+  @server(
+    handler: foo
+  )
+  // 非导出型body
+  post /foo/:id (foo) returns (bar)
+  
+  @doc "bar"
+  @handler bar
+  post /bar returns ([]int)// 不推荐数组作为响应体
+  
+  @handler fooBar
+  post /foo/bar (Foo) returns // 可以省略'returns'
+}
+```
+
+eg2:规范写法(推荐)
+
+``` api
+@server(
+  jwt: Auth
+  group: foo
+  middleware: AuthMiddleware
+)
+service foo-api{
+  @doc "foo"
+  @handler: foo
+  post /foo/:id (Foo) returns (Bar)
+}
+
+service foo-api{
+  @handler ping
+  get /ping
+  
+  @doc "foo"
+  @handler: bar
+  post /bar/:id (Foo)
+}
+
+```
+
+**错误语法示例** ❌
+
+``` api
+// 不支持空的server语法块
+@server(
+)
+// 不支持空的service语法块
+service foo-api{
+}
+
+service foo-api{
+  @doc kkkk // 简版doc必须用英文双引号引起来
+  @handler foo
+  post /foo
+  
+  @handler foo // 重复的handler
+  post /bar
+  
+  @handler fooBar
+  post /bar // 重复的路由
+  
+  // @handler和@doc顺序错误
+  @handler someHandler
+  @doc "some doc"
+  post /some/path
+  
+  // handler缺失
+  post /some/path/:id
+  
+  @handler reqTest
+  post /foo/req (*Foo) // 不支持除普通结构体外的其他数据类型作为请求体
+  
+  @handler replyTest
+  post /foo/reply returns (*Foo) // 不支持除普通结构体、数组(向前兼容,后续考虑废弃)外的其他数据类型作为响应体
+}
+```
+
+## 隐藏通道
+
+隐藏通道目前主要为空百符号,换行符号以及注释,这里我们只说注释,因为空白符号和换行符号我们目前拿来也无用。
+
+### 单行注释
+
+**语法定义**
+
+``` antlrv4
+'//' ~[\r\n]*
+```
+
+**语法说明**
+由语法定义可知道,单行注释必须要以`//`开头,内容为不能包含换行符
+
+**正确语法示例** ✅
+
+``` api
+// doc
+// comment
+```
+
+**错误语法示例** ❌
+
+``` api
+// break
+line comments
+```
+
+### java风格文档注释
+
+**语法定义**
+
+``` antlrv4
+'/*' .*? '*/'
+```
+
+**语法说明**
+
+由语法定义可知道,单行注释必须要以`/*`开头,`*/`结尾的任意字符。
+
+**正确语法示例** ✅
+
+``` api
+/**
+ * java-style doc
+ */
+```
+
+**错误语法示例** ❌
+
+``` api
+/*
+ * java-style doc */
+ */
+```
+
+## Doc&Comment
+
+如果想获取某一个元素的doc或者comment开发人员需要怎么定义?
+
+**Doc**
+> 我们规定上一个语法块(非隐藏通道内容)的行数line+1到当前语法块第一个元素前的所有注释(当行,或者多行)均为doc, 且保留了`//`、`/*`、`*/`原始标记。
+
+**Comment**
+> 我们规定当前语法块最后一个元素所在行开始的一个注释块(当行,或者多行)为comment 且保留了`//`、`/*`、`*/`原始标记。
+
+语法块Doc和Comment的支持情况
+
+|语法块|parent语法块|Doc|Comment|
+|:---|:---|:---|:---|
+|syntaxLit|api|✅|✅|
+|kvLit|infoSpec|✅|✅|
+|importLit|importSpec|✅|✅|
+|typeLit|api|✅|❌|
+|typeLit|typeBlock|✅|❌|
+|field|typeLit|✅|✅|
+|key-value|atServer|✅|✅|
+|atHandler|serviceRoute|✅|✅|
+|route|serviceRoute|✅|✅|
+
+以下为对应语法块解析后细带doc和comment的写法
+``` api
+// syntaxLit doc
+syntax = "v1" // syntaxLit commnet
+
+info(
+  // kvLit doc
+  author: songmeizi // kvLit comment
+)
+
+// typeLit doc
+type Foo {}
+
+type(
+  // typeLit doc
+  Bar{}
+  
+  FooBar{
+    // filed doc
+    Name int // filed comment
+  }
+)
+
+@server(
+  /**
+   * kvLit doc
+   * 开启jwt鉴权
+   */
+  jwt: Auth /**kvLit comment*/
+)
+service foo-api{
+  // atHandler doc
+  @handler foo //atHandler comment
+  
+  /*
+   * route doc
+   * post请求
+   * path为 /foo
+   * 请求体:Foo
+   * 响应体:Foo
+   */
+  post /foo (Foo) returns (Foo) // route comment
+}
+```

+ 0 - 113
tools/goctl/api/parser/rootstate.go

@@ -1,113 +0,0 @@
-package parser
-
-import (
-	"bufio"
-	"fmt"
-	"strings"
-
-	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-)
-
-type rootState struct {
-	*baseState
-}
-
-func newRootState(r *bufio.Reader, lineNumber *int) state {
-	var state = newBaseState(r, lineNumber)
-	return rootState{
-		baseState: state,
-	}
-}
-
-func (s rootState) process(api *spec.ApiSpec) (state, error) {
-	var annos []spec.Annotation
-	var builder strings.Builder
-	for {
-		ch, err := s.readSkipComment()
-		if err != nil {
-			return nil, err
-		}
-
-		switch {
-		case isSpace(ch):
-			if builder.Len() == 0 {
-				continue
-			}
-
-			token := builder.String()
-			builder.Reset()
-			return s.processToken(token, annos)
-		case ch == at:
-			if builder.Len() > 0 {
-				return nil, fmt.Errorf("%q before %q", builder.String(), at)
-			}
-
-			var annoName string
-		annoLoop:
-			for {
-				next, err := s.readSkipComment()
-				if err != nil {
-					return nil, err
-				}
-
-				switch {
-				case isSpace(next):
-					if builder.Len() > 0 {
-						annoName = builder.String()
-						builder.Reset()
-					}
-				case next == leftParenthesis:
-					if err := s.unread(); err != nil {
-						return nil, err
-					}
-
-					if builder.Len() > 0 {
-						annoName = builder.String()
-						builder.Reset()
-					}
-					attrs, err := s.parseProperties()
-					if err != nil {
-						return nil, err
-					}
-
-					annos = append(annos, spec.Annotation{
-						Name:       annoName,
-						Properties: attrs,
-					})
-					break annoLoop
-				default:
-					builder.WriteRune(next)
-				}
-			}
-		case ch == leftParenthesis:
-			if builder.Len() == 0 {
-				return nil, fmt.Errorf("incorrect %q at the beginning of the line", leftParenthesis)
-			}
-
-			if err := s.unread(); err != nil {
-				return nil, err
-			}
-
-			token := builder.String()
-			builder.Reset()
-			return s.processToken(token, annos)
-		case isLetterDigit(ch):
-			builder.WriteRune(ch)
-		case isNewline(ch):
-			if builder.Len() > 0 {
-				return nil, fmt.Errorf("incorrect newline after %q", builder.String())
-			}
-		}
-	}
-}
-
-func (s rootState) processToken(token string, annos []spec.Annotation) (state, error) {
-	switch token {
-	case infoDirective:
-		return newInfoState(s.baseState), nil
-	case serviceDirective:
-		return newServiceState(s.baseState, annos), nil
-	default:
-		return nil, fmt.Errorf("wrong directive %q", token)
-	}
-}

+ 0 - 130
tools/goctl/api/parser/servicestate.go

@@ -1,130 +0,0 @@
-package parser
-
-import (
-	"bufio"
-	"bytes"
-	"fmt"
-	"io"
-	"strings"
-
-	"github.com/tal-tech/go-zero/core/stringx"
-	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-)
-
-type serviceState struct {
-	*baseState
-	annos []spec.Annotation
-}
-
-func newServiceState(state *baseState, annos []spec.Annotation) state {
-	return &serviceState{
-		baseState: state,
-		annos:     annos,
-	}
-}
-
-func (s *serviceState) process(api *spec.ApiSpec) (state, error) {
-	var name string
-	var routes []spec.Route
-	parser := &serviceEntityParser{
-		acceptName: func(n string) {
-			name = n
-		},
-		acceptRoute: func(route spec.Route) {
-			routes = append(routes, route)
-		},
-	}
-	ent := newEntity(s.baseState, api, parser)
-	if err := ent.process(); err != nil {
-		return nil, err
-	}
-
-	api.Service = spec.Service{
-		Name: name,
-		Groups: append(api.Service.Groups, spec.Group{
-			Annotations: s.annos,
-			Routes:      routes,
-		}),
-	}
-
-	return newRootState(s.r, s.lineNumber), nil
-}
-
-type serviceEntityParser struct {
-	acceptName  func(name string)
-	acceptRoute func(route spec.Route)
-}
-
-func (p *serviceEntityParser) parseLine(line string, api *spec.ApiSpec, annos []spec.Annotation) error {
-	var defaultErr = fmt.Errorf("wrong line %q, %q", line, routeSyntax)
-
-	line = strings.TrimSpace(line)
-	var buffer = new(bytes.Buffer)
-	buffer.WriteString(line)
-	reader := bufio.NewReader(buffer)
-	var builder strings.Builder
-	var fields = make([]string, 0)
-	for {
-		ch, _, err := reader.ReadRune()
-		if err != nil {
-			if err == io.EOF {
-				if builder.Len() > 0 {
-					token := strings.TrimSpace(builder.String())
-					if len(token) > 0 && token != returnsTag {
-						fields = append(fields, token)
-					}
-				}
-				break
-			}
-			return err
-		}
-
-		switch {
-		case isSpace(ch), ch == leftParenthesis, ch == rightParenthesis, ch == semicolon:
-			if builder.Len() == 0 {
-				continue
-			}
-			token := builder.String()
-			builder.Reset()
-			fields = append(fields, token)
-		default:
-			builder.WriteRune(ch)
-		}
-	}
-
-	if len(fields) < 2 {
-		return defaultErr
-	}
-	method := fields[0]
-	path := fields[1]
-	var req string
-	var resp string
-
-	if len(fields) > 2 {
-		req = fields[2]
-	}
-	if stringx.Contains(fields, returnsTag) {
-		if fields[len(fields)-1] != returnsTag {
-			resp = fields[len(fields)-1]
-		} else {
-			return defaultErr
-		}
-		if fields[2] == returnsTag {
-			req = ""
-		}
-	}
-
-	p.acceptRoute(spec.Route{
-		Annotations:  annos,
-		Method:       method,
-		Path:         path,
-		RequestType:  GetType(api, req),
-		ResponseType: GetType(api, resp),
-	})
-
-	return nil
-}
-
-func (p *serviceEntityParser) setEntityName(name string) {
-	p.acceptName(name)
-}

+ 0 - 7
tools/goctl/api/parser/state.go

@@ -1,7 +0,0 @@
-package parser
-
-import "github.com/tal-tech/go-zero/tools/goctl/api/spec"
-
-type state interface {
-	process(api *spec.ApiSpec) (state, error)
-}

+ 0 - 339
tools/goctl/api/parser/typeparser.go

@@ -1,339 +0,0 @@
-package parser
-
-import (
-	"errors"
-	"fmt"
-	"go/ast"
-	"go/parser"
-	"go/token"
-	"sort"
-	"strings"
-
-	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-)
-
-var (
-	ErrStructNotFound      = errors.New("struct not found")
-	ErrUnSupportInlineType = errors.New("unsupport inline type")
-	interfaceExpr          = `interface{}`
-	objectM                = make(map[string]*spec.Type)
-)
-
-const (
-	golangF = `package ast
-	%s
-`
-	pkgPrefix = "package"
-)
-
-type StructParser struct {
-	Src string
-}
-
-func (sp *StructParser) Parse() ([]spec.Type, error) {
-	if !strings.HasPrefix(sp.Src, pkgPrefix) {
-		sp.Src = fmt.Sprintf(golangF, sp.Src)
-	}
-
-	fSet := token.NewFileSet()
-	f, err := parser.ParseFile(fSet, "", sp.Src, parser.ParseComments)
-	if err != nil {
-		return nil, err
-	}
-	commentMap := ast.NewCommentMap(fSet, f, f.Comments)
-	f.Comments = commentMap.Filter(f).Comments()
-	scope := f.Scope
-	if scope == nil {
-		return nil, ErrStructNotFound
-	}
-	objects := scope.Objects
-	structs := make([]*spec.Type, 0)
-	for structName, obj := range objects {
-		st, err := sp.parseObject(structName, obj)
-		if err != nil {
-			return nil, err
-		}
-		structs = append(structs, st)
-	}
-	sort.Slice(structs, func(i, j int) bool {
-		return structs[i].Name < structs[j].Name
-	})
-	resp := make([]spec.Type, 0)
-	for _, item := range structs {
-		resp = append(resp, *item)
-	}
-	return resp, nil
-}
-
-func (sp *StructParser) parseObject(structName string, obj *ast.Object) (*spec.Type, error) {
-	if data, ok := objectM[structName]; ok {
-		return data, nil
-	}
-	var st spec.Type
-	st.Name = structName
-	if obj.Decl == nil {
-		objectM[structName] = &st
-		return &st, nil
-	}
-	decl, ok := obj.Decl.(*ast.TypeSpec)
-	if !ok {
-		objectM[structName] = &st
-		return &st, nil
-	}
-	if decl.Type == nil {
-		objectM[structName] = &st
-		return &st, nil
-	}
-	tp, ok := decl.Type.(*ast.StructType)
-	if !ok {
-		objectM[structName] = &st
-		return &st, nil
-	}
-	fields := tp.Fields
-	if fields == nil {
-		objectM[structName] = &st
-		return &st, nil
-	}
-	fieldList := fields.List
-	members, err := sp.parseFields(fieldList)
-	if err != nil {
-		return nil, err
-	}
-	st.Members = members
-	objectM[structName] = &st
-	return &st, nil
-}
-
-func (sp *StructParser) parseFields(fields []*ast.Field) ([]spec.Member, error) {
-	members := make([]spec.Member, 0)
-	for _, field := range fields {
-		docs := parseCommentOrDoc(field.Doc)
-		comments := parseCommentOrDoc(field.Comment)
-		name := parseName(field.Names)
-		tp, stringExpr, err := sp.parseType(field.Type)
-		if err != nil {
-			return nil, err
-		}
-		tag := parseTag(field.Tag)
-		isInline := name == ""
-		if isInline {
-			var err error
-			name, err = sp.getInlineName(tp)
-			if err != nil {
-				return nil, err
-			}
-		}
-		members = append(members, spec.Member{
-			Name:     name,
-			Type:     stringExpr,
-			Expr:     tp,
-			Tag:      tag,
-			Comments: comments,
-			Docs:     docs,
-			IsInline: isInline,
-		})
-
-	}
-	return members, nil
-}
-
-func (sp *StructParser) getInlineName(tp interface{}) (string, error) {
-	switch v := tp.(type) {
-	case *spec.Type:
-		return v.Name, nil
-	case *spec.PointerType:
-		return sp.getInlineName(v.Star)
-	case *spec.StructType:
-		return v.StringExpr, nil
-	default:
-		return "", ErrUnSupportInlineType
-	}
-}
-
-func (sp *StructParser) getInlineTypePrefix(tp interface{}) (string, error) {
-	if tp == nil {
-		return "", nil
-	}
-	switch tp.(type) {
-	case *ast.Ident:
-		return "", nil
-	case *ast.StarExpr:
-		return "*", nil
-	case *ast.TypeSpec:
-		return "", nil
-	default:
-		return "", ErrUnSupportInlineType
-	}
-}
-
-func parseTag(basicLit *ast.BasicLit) string {
-	if basicLit == nil {
-		return ""
-	}
-	return basicLit.Value
-}
-
-// returns
-// resp1: type can convert to *spec.PointerType|*spec.BasicType|*spec.MapType|*spec.ArrayType|*spec.InterfaceType
-// resp2: type's string expression,like int、string、[]int64、map[string]User、*User
-// resp3: error
-func (sp *StructParser) parseType(expr ast.Expr) (interface{}, string, error) {
-	if expr == nil {
-		return nil, "", errors.New("parse error " + sp.Src)
-	}
-	exprStr := sp.Src[expr.Pos():expr.End()]
-	switch v := expr.(type) {
-	case *ast.StarExpr:
-		star, stringExpr, err := sp.parseType(v.X)
-		if err != nil {
-			return nil, "", err
-		}
-		e := fmt.Sprintf("*%s", stringExpr)
-		return &spec.PointerType{Star: star, StringExpr: e}, e, nil
-	case *ast.Ident:
-		if isBasicType(v.Name) {
-			return &spec.BasicType{Name: v.Name, StringExpr: v.Name}, v.Name, nil
-		} else if v.Obj != nil {
-			obj := v.Obj
-			if obj.Name != v.Name { // 防止引用自己而无限递归
-				specType, err := sp.parseObject(v.Name, v.Obj)
-				if err != nil {
-					return nil, "", err
-				} else {
-					return specType, v.Obj.Name, nil
-				}
-			} else {
-				inlineType, err := sp.getInlineTypePrefix(obj.Decl)
-				if err != nil {
-					return nil, "", err
-				}
-				return &spec.StructType{
-					StringExpr: fmt.Sprintf("%s%s", inlineType, v.Name),
-				}, v.Name, nil
-			}
-		} else {
-			return nil, "", fmt.Errorf(" [%s] - member is not exist, expr is %s", v.Name, exprStr)
-		}
-	case *ast.MapType:
-		key, keyStringExpr, err := sp.parseType(v.Key)
-		if err != nil {
-			return nil, "", err
-		}
-
-		value, valueStringExpr, err := sp.parseType(v.Value)
-		if err != nil {
-			return nil, "", err
-		}
-
-		keyType, ok := key.(*spec.BasicType)
-		if !ok {
-			return nil, "", fmt.Errorf("[%+v] - unsupported type of map key, expr is  %s", v.Key, exprStr)
-		}
-
-		e := fmt.Sprintf("map[%s]%s", keyStringExpr, valueStringExpr)
-		return &spec.MapType{
-			Key:        keyType.Name,
-			Value:      value,
-			StringExpr: e,
-		}, e, nil
-	case *ast.ArrayType:
-		arrayType, stringExpr, err := sp.parseType(v.Elt)
-		if err != nil {
-			return nil, "", err
-		}
-
-		e := fmt.Sprintf("[]%s", stringExpr)
-		return &spec.ArrayType{ArrayType: arrayType, StringExpr: e}, e, nil
-	case *ast.InterfaceType:
-		return &spec.InterfaceType{StringExpr: interfaceExpr}, interfaceExpr, nil
-	case *ast.ChanType:
-		return nil, "", errors.New("[chan] - unsupported type, expr is " + exprStr)
-	case *ast.FuncType:
-		return nil, "", errors.New("[func] - unsupported type, expr is " + exprStr)
-	case *ast.StructType: // todo can optimize
-		return nil, "", errors.New("[struct] - unsupported inline struct type, expr is " + exprStr)
-	case *ast.SelectorExpr:
-		x := v.X
-		sel := v.Sel
-		xIdent, ok := x.(*ast.Ident)
-		if ok {
-			name := xIdent.Name
-			if name != "time" && sel.Name != "Time" {
-				return nil, "", fmt.Errorf("[outter package] - package: %s, unsupport type", exprStr)
-			}
-
-			tm := fmt.Sprintf("time.Time")
-			return &spec.TimeType{
-				StringExpr: tm,
-			}, tm, nil
-		}
-		return nil, "", errors.New("parse error " + exprStr)
-	default:
-		return nil, "", errors.New("parse error " + exprStr)
-	}
-}
-
-func isBasicType(tp string) bool {
-	switch tp {
-	case
-		"bool",
-		"uint8",
-		"uint16",
-		"uint32",
-		"uint64",
-		"int8",
-		"int16",
-		"int32",
-		"int64",
-		"float32",
-		"float64",
-		"complex64",
-		"complex128",
-		"string",
-		"int",
-		"uint",
-		"uintptr",
-		"byte",
-		"rune",
-		"Type",
-		"Type1",
-		"IntegerType",
-		"FloatType",
-		"ComplexType":
-		return true
-	default:
-		return false
-	}
-}
-func parseName(names []*ast.Ident) string {
-	if len(names) == 0 {
-		return ""
-	}
-	name := names[0]
-	return parseIdent(name)
-}
-
-func parseIdent(ident *ast.Ident) string {
-	if ident == nil {
-		return ""
-	}
-	return ident.Name
-}
-
-func parseCommentOrDoc(cg *ast.CommentGroup) []string {
-	if cg == nil {
-		return nil
-	}
-	comments := make([]string, 0)
-	for _, comment := range cg.List {
-		if comment == nil {
-			continue
-		}
-		text := strings.TrimSpace(comment.Text)
-		if text == "" {
-			continue
-		}
-		comments = append(comments, text)
-	}
-	return comments
-}

+ 0 - 58
tools/goctl/api/parser/util.go

@@ -1,65 +1,7 @@
 package parser
 
 import (
-	"bufio"
-
 	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
 )
 
 var emptyType spec.Type
-
-func GetType(api *spec.ApiSpec, t string) spec.Type {
-	for _, tp := range api.Types {
-		if tp.Name == t {
-			return tp
-		}
-	}
-
-	return emptyType
-}
-
-func isLetterDigit(r rune) bool {
-	return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || ('0' <= r && r <= '9')
-}
-
-func isSpace(r rune) bool {
-	return r == ' ' || r == '\t'
-}
-
-func isSlash(r rune) bool {
-	return r == '/'
-}
-
-func isNewline(r rune) bool {
-	return r == '\n' || r == '\r'
-}
-
-func read(r *bufio.Reader) (rune, error) {
-	ch, _, err := r.ReadRune()
-	return ch, err
-}
-
-func readLine(r *bufio.Reader) (string, error) {
-	line, _, err := r.ReadLine()
-	if err != nil {
-		return "", err
-	} else {
-		return string(line), nil
-	}
-}
-
-func skipSpaces(r *bufio.Reader) error {
-	for {
-		next, err := read(r)
-		if err != nil {
-			return err
-		}
-		if !isSpace(next) {
-			return unread(r)
-		}
-	}
-}
-
-func unread(r *bufio.Reader) error {
-	return r.UnreadRune()
-}

+ 0 - 55
tools/goctl/api/parser/validator.go

@@ -1,55 +0,0 @@
-package parser
-
-import (
-	"errors"
-	"fmt"
-	"strings"
-
-	"github.com/tal-tech/go-zero/core/stringx"
-	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-	"github.com/tal-tech/go-zero/tools/goctl/api/util"
-)
-
-func (p *Parser) validate(api *spec.ApiSpec) (err error) {
-	var builder strings.Builder
-	for _, tp := range api.Types {
-		if ok, name := p.validateDuplicateProperty(tp); !ok {
-			fmt.Fprintf(&builder, `duplicate property "%s" of type "%s"`+"\n", name, tp.Name)
-		}
-	}
-	if ok, info := p.validateDuplicateRouteHandler(api); !ok {
-		fmt.Fprintf(&builder, info)
-	}
-	if len(builder.String()) > 0 {
-		return errors.New(builder.String())
-	}
-	return nil
-}
-
-func (p *Parser) validateDuplicateProperty(tp spec.Type) (bool, string) {
-	var names []string
-	for _, member := range tp.Members {
-		if stringx.Contains(names, member.Name) {
-			return false, member.Name
-		} else {
-			names = append(names, member.Name)
-		}
-	}
-	return true, ""
-}
-
-func (p *Parser) validateDuplicateRouteHandler(api *spec.ApiSpec) (bool, string) {
-	var names []string
-	for _, r := range api.Service.Routes() {
-		handler, ok := util.GetAnnotationValue(r.Annotations, "server", "handler")
-		if !ok {
-			return false, fmt.Sprintf("missing handler annotation for %s", r.Path)
-		}
-		if stringx.Contains(names, handler) {
-			return false, fmt.Sprintf(`duplicated handler for name "%s"`, handler)
-		} else {
-			names = append(names, handler)
-		}
-	}
-	return true, ""
-}

+ 0 - 20
tools/goctl/api/parser/vars.go

@@ -1,20 +0,0 @@
-package parser
-
-const (
-	infoDirective     = "info"
-	serviceDirective  = "service"
-	typeDirective     = "type"
-	typeStruct        = "struct"
-	routeSyntax       = "route syntax: [get/post/delete] /path(request) returns[(response)]"
-	returnsTag        = "returns"
-	at                = '@'
-	colon             = ':'
-	leftParenthesis   = '('
-	rightParenthesis  = ')'
-	leftBrace         = "{"
-	rightBrace        = '}'
-	multilineBeginTag = '>'
-	multilineEndTag   = '<'
-	semicolon         = ';'
-	newline           = "\n"
-)

+ 79 - 4
tools/goctl/api/spec/fn.go

@@ -9,8 +9,9 @@ import (
 )
 
 const bodyTagKey = "json"
+const formTagKey = "form"
 
-var definedKeys = []string{"json", "form", "path"}
+var definedKeys = []string{bodyTagKey, formTagKey, "path"}
 
 func (s Service) Routes() []Route {
 	var result []Route
@@ -45,6 +46,22 @@ func (m Member) IsOptional() bool {
 	return false
 }
 
+func (m Member) IsOmitEmpty() bool {
+	if !m.IsBodyMember() {
+		return false
+	}
+
+	tag := m.Tags()
+	for _, item := range tag {
+		if item.Key == bodyTagKey {
+			if stringx.Contains(item.Options, "omitempty") {
+				return true
+			}
+		}
+	}
+	return false
+}
+
 func (m Member) IsOmitempty() bool {
 	if !m.IsBodyMember() {
 		return false
@@ -76,7 +93,7 @@ func (m Member) GetPropertyName() (string, error) {
 }
 
 func (m Member) GetComment() string {
-	return strings.TrimSpace(strings.Join(m.Comments, "; "))
+	return strings.TrimSpace(m.Comment)
 }
 
 func (m Member) IsBodyMember() bool {
@@ -93,7 +110,21 @@ func (m Member) IsBodyMember() bool {
 	return false
 }
 
-func (t Type) GetBodyMembers() []Member {
+func (m Member) IsFormMember() bool {
+	if m.IsInline {
+		return false
+	}
+
+	tags := m.Tags()
+	for _, tag := range tags {
+		if tag.Key == formTagKey {
+			return true
+		}
+	}
+	return false
+}
+
+func (t DefineStruct) GetBodyMembers() []Member {
 	var result []Member
 	for _, member := range t.Members {
 		if member.IsBodyMember() {
@@ -103,7 +134,17 @@ func (t Type) GetBodyMembers() []Member {
 	return result
 }
 
-func (t Type) GetNonBodyMembers() []Member {
+func (t DefineStruct) GetFormMembers() []Member {
+	var result []Member
+	for _, member := range t.Members {
+		if member.IsFormMember() {
+			result = append(result, member)
+		}
+	}
+	return result
+}
+
+func (t DefineStruct) GetNonBodyMembers() []Member {
 	var result []Member
 	for _, member := range t.Members {
 		if !member.IsBodyMember() {
@@ -112,3 +153,37 @@ func (t Type) GetNonBodyMembers() []Member {
 	}
 	return result
 }
+
+func (r Route) JoinedDoc() string {
+	return strings.Join(r.Docs, " ")
+}
+
+func (r Route) GetAnnotation(key string) string {
+	if r.Annotation.Properties == nil {
+		return ""
+	}
+	return r.Annotation.Properties[key]
+}
+
+func (g Group) GetAnnotation(key string) string {
+	if g.Annotation.Properties == nil {
+		return ""
+	}
+	return g.Annotation.Properties[key]
+}
+
+func (r Route) ResponseTypeName() string {
+	if r.ResponseType == nil {
+		return ""
+	}
+
+	return r.ResponseType.Name()
+}
+
+func (r Route) RequestTypeName() string {
+	if r.RequestType == nil {
+		return ""
+	}
+
+	return r.RequestType.Name()
+}

+ 25 - 0
tools/goctl/api/spec/name.go

@@ -0,0 +1,25 @@
+package spec
+
+func (t PrimitiveType) Name() string {
+	return t.RawName
+}
+
+func (t DefineStruct) Name() string {
+	return t.RawName
+}
+
+func (t MapType) Name() string {
+	return t.RawName
+}
+
+func (t ArrayType) Name() string {
+	return t.RawName
+}
+
+func (t PointerType) Name() string {
+	return t.RawName
+}
+
+func (t InterfaceType) Name() string {
+	return t.RawName
+}

+ 55 - 74
tools/goctl/api/spec/spec.go

@@ -1,59 +1,66 @@
 package spec
 
 type (
+	Doc []string
+
 	Annotation struct {
-		Name       string
 		Properties map[string]string
-		Value      string
+	}
+
+	ApiSyntax struct {
+		Version string
 	}
 
 	ApiSpec struct {
 		Info    Info
+		Syntax  ApiSyntax
+		Imports []Import
 		Types   []Type
 		Service Service
 	}
 
+	Import struct {
+		Value string
+	}
+
 	Group struct {
-		Annotations []Annotation
-		Routes      []Route
+		Annotation Annotation
+		Routes     []Route
 	}
 
 	Info struct {
-		Title   string
-		Desc    string
+		// Deprecated: use Properties instead
+		Title string
+		// Deprecated: use Properties instead
+		Desc string
+		// Deprecated: use Properties instead
 		Version string
-		Author  string
-		Email   string
+		// Deprecated: use Properties instead
+		Author string
+		// Deprecated: use Properties instead
+		Email      string
+		Properties map[string]string
 	}
 
 	Member struct {
-		Annotations []Annotation
-		Name        string
+		Name string
 		// 数据类型字面值,如:string、map[int]string、[]int64、[]*User
-		Type string
-		// it can be asserted as BasicType: int、bool、
-		// PointerType: *string、*User、
-		// MapType: map[${BasicType}]interface、
-		// ArrayType:[]int、[]User、[]*User
-		// InterfaceType: interface{}
-		// Type
-		Expr interface{}
-		Tag  string
-		// Deprecated
-		Comment string // 换成标准struct中将废弃
-		// 成员尾部注释说明
-		Comments []string
+		Type    Type
+		Tag     string
+		Comment string
 		// 成员头顶注释说明
-		Docs     []string
+		Docs     Doc
 		IsInline bool
 	}
 
 	Route struct {
-		Annotations  []Annotation
+		Annotation   Annotation
 		Method       string
 		Path         string
 		RequestType  Type
 		ResponseType Type
+		Docs         Doc
+		Handler      string
 	}
 
 	Service struct {
@@ -61,71 +68,45 @@ type (
 		Groups []Group
 	}
 
-	Type struct {
-		Name        string
-		Annotations []Annotation
-		Members     []Member
+	Type interface {
+		Name() string
 	}
 
-	// 系统预设基本数据类型
-	BasicType struct {
-		StringExpr string
-		Name       string
+	DefineStruct struct {
+		RawName string
+		Members []Member
+		Docs    Doc
 	}
 
-	PointerType struct {
-		StringExpr string
-		// it can be asserted as BasicType: int、bool、
-		// PointerType: *string、*User、
-		// MapType: map[${BasicType}]interface、
-		// ArrayType:[]int、[]User、[]*User
-		// InterfaceType: interface{}
-		// Type
-		Star interface{}
+	// 系统预设基本数据类型 bool int32 int64 float32
+	PrimitiveType struct {
+		RawName string
 	}
 
 	MapType struct {
-		StringExpr string
-		// only support the BasicType
+		RawName string
+		// only support the PrimitiveType
 		Key string
-		// it can be asserted as BasicType: int、bool、
+		// it can be asserted as PrimitiveType: int、bool、
 		// PointerType: *string、*User、
-		// MapType: map[${BasicType}]interface、
+		// MapType: map[${PrimitiveType}]interface、
 		// ArrayType:[]int、[]User、[]*User
 		// InterfaceType: interface{}
 		// Type
-		Value interface{}
+		Value Type
 	}
+
 	ArrayType struct {
-		StringExpr string
-		// it can be asserted as BasicType: int、bool、
-		// PointerType: *string、*User、
-		// MapType: map[${BasicType}]interface、
-		// ArrayType:[]int、[]User、[]*User
-		// InterfaceType: interface{}
-		// Type
-		ArrayType interface{}
+		RawName string
+		Value   Type
 	}
+
 	InterfaceType struct {
-		StringExpr string
-		// do nothing,just for assert
-	}
-	TimeType struct {
-		StringExpr string
+		RawName string
 	}
-	StructType struct {
-		StringExpr string
+
+	PointerType struct {
+		RawName string
+		Type    Type
 	}
 )
-
-func (spec *ApiSpec) ContainsTime() bool {
-	for _, item := range spec.Types {
-		members := item.Members
-		for _, member := range members {
-			if _, ok := member.Expr.(*TimeType); ok {
-				return true
-			}
-		}
-	}
-	return false
-}

+ 2 - 5
tools/goctl/api/tsgen/gen.go

@@ -20,15 +20,12 @@ func TsCommand(c *cli.Context) error {
 	if len(apiFile) == 0 {
 		return errors.New("missing -api")
 	}
+
 	if len(dir) == 0 {
 		return errors.New("missing -dir")
 	}
 
-	p, err := parser.NewParser(apiFile)
-	if err != nil {
-		return err
-	}
-	api, err := p.Parse()
+	api, err := parser.Parse(apiFile)
 	if err != nil {
 		fmt.Println(aurora.Red("Failed"))
 		return err

+ 5 - 17
tools/goctl/api/tsgen/gencomponents.go

@@ -1,7 +1,6 @@
 package tsgen
 
 import (
-	"errors"
 	"path"
 	"strings"
 	"text/template"
@@ -19,19 +18,12 @@ const (
 )
 
 func genComponents(dir string, api *spec.ApiSpec) error {
-	types := apiutil.GetSharedTypes(api)
+	types := api.Types
 	if len(types) == 0 {
 		return nil
 	}
 
-	val, err := buildTypes(types, func(name string) (*spec.Type, error) {
-		for _, ty := range api.Types {
-			if strings.ToLower(ty.Name) == strings.ToLower(name) {
-				return &ty, nil
-			}
-		}
-		return nil, errors.New("inline type " + name + " not exist, please correct api file")
-	})
+	val, err := buildTypes(types)
 	if err != nil {
 		return err
 	}
@@ -57,7 +49,7 @@ func genComponents(dir string, api *spec.ApiSpec) error {
 	})
 }
 
-func buildTypes(types []spec.Type, inlineType func(string) (*spec.Type, error)) (string, error) {
+func buildTypes(types []spec.Type) (string, error) {
 	var builder strings.Builder
 	first := true
 	for _, tp := range types {
@@ -66,12 +58,8 @@ func buildTypes(types []spec.Type, inlineType func(string) (*spec.Type, error))
 		} else {
 			builder.WriteString("\n")
 		}
-		if err := writeType(&builder, tp, func(name string) (*spec.Type, error) {
-			return inlineType(name)
-		}, func(tp string) string {
-			return ""
-		}); err != nil {
-			return "", apiutil.WrapErr(err, "Type "+tp.Name+" generate error")
+		if err := writeType(&builder, tp); err != nil {
+			return "", apiutil.WrapErr(err, "Type "+tp.Name()+" generate error")
 		}
 	}
 

+ 36 - 73
tools/goctl/api/tsgen/genpacket.go

@@ -1,7 +1,6 @@
 package tsgen
 
 import (
-	"errors"
 	"fmt"
 	"path"
 	"strings"
@@ -15,8 +14,6 @@ import (
 const (
 	handlerTemplate = `{{.imports}}
 
-{{.types}}
-
 {{.apis}}
 `
 )
@@ -35,36 +32,6 @@ func genHandler(dir, webApi, caller string, api *spec.ApiSpec, unwrapApi bool) e
 	}
 	defer fp.Close()
 
-	var localTypes []spec.Type
-	for _, route := range api.Service.Routes() {
-		rts := apiutil.GetLocalTypes(api, route)
-		localTypes = append(localTypes, rts...)
-	}
-
-	var prefixForType = func(ty string) string {
-		if _, pri := primitiveType(ty); pri {
-			return ""
-		}
-		for _, item := range localTypes {
-			if util.Title(item.Name) == ty {
-				return ""
-			}
-		}
-		return packagePrefix
-	}
-
-	types, err := genTypes(localTypes, func(name string) (*spec.Type, error) {
-		for _, ty := range api.Types {
-			if strings.ToLower(ty.Name) == strings.ToLower(name) {
-				return &ty, nil
-			}
-		}
-		return nil, errors.New("inline type " + name + " not exist, please correct api file")
-	}, prefixForType)
-	if err != nil {
-		return err
-	}
-
 	imports := ""
 	if len(caller) == 0 {
 		caller = "webapi"
@@ -76,8 +43,8 @@ func genHandler(dir, webApi, caller string, api *spec.ApiSpec, unwrapApi bool) e
 	if len(webApi) > 0 {
 		imports += `import ` + importCaller + ` from ` + "\"" + webApi + "\""
 	}
-	shardTypes := apiutil.GetSharedTypes(api)
-	if len(shardTypes) != 0 {
+
+	if len(api.Types) != 0 {
 		if len(imports) > 0 {
 			imports += "\n"
 		}
@@ -85,7 +52,7 @@ func genHandler(dir, webApi, caller string, api *spec.ApiSpec, unwrapApi bool) e
 		imports += fmt.Sprintf(`import * as components from "%s"`, "./"+outputFile)
 	}
 
-	apis, err := genApi(api, caller, prefixForType)
+	apis, err := genApi(api, caller)
 	if err != nil {
 		return err
 	}
@@ -93,54 +60,35 @@ func genHandler(dir, webApi, caller string, api *spec.ApiSpec, unwrapApi bool) e
 	t := template.Must(template.New("handlerTemplate").Parse(handlerTemplate))
 	return t.Execute(fp, map[string]string{
 		"webApi":  webApi,
-		"types":   strings.TrimSpace(types),
 		"imports": imports,
 		"apis":    strings.TrimSpace(apis),
 	})
 }
 
-func genTypes(localTypes []spec.Type, inlineType func(string) (*spec.Type, error), prefixForType func(string) string) (string, error) {
-	var builder strings.Builder
-	var first bool
-
-	for _, tp := range localTypes {
-		if first {
-			first = false
-		} else {
-			fmt.Fprintln(&builder)
-		}
-		if err := writeType(&builder, tp, func(name string) (s *spec.Type, err error) {
-			return inlineType(name)
-		}, prefixForType); err != nil {
-			return "", err
-		}
-	}
-	types := builder.String()
-	return types, nil
-}
-
-func genApi(api *spec.ApiSpec, caller string, prefixForType func(string) string) (string, error) {
+func genApi(api *spec.ApiSpec, caller string) (string, error) {
 	var builder strings.Builder
 	for _, group := range api.Service.Groups {
 		for _, route := range group.Routes {
-			handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
-			if !ok {
+			handler := route.Handler
+			if len(handler) == 0 {
 				return "", fmt.Errorf("missing handler annotation for route %q", route.Path)
 			}
+
 			handler = util.Untitle(handler)
 			handler = strings.Replace(handler, "Handler", "", 1)
 			comment := commentForRoute(route)
 			if len(comment) > 0 {
 				fmt.Fprintf(&builder, "%s\n", comment)
 			}
-			fmt.Fprintf(&builder, "export function %s(%s) {\n", handler, paramsForRoute(route, prefixForType))
+			fmt.Fprintf(&builder, "export function %s(%s) {\n", handler, paramsForRoute(route))
 			writeIndent(&builder, 1)
 			responseGeneric := "<null>"
-			if len(route.ResponseType.Name) > 0 {
-				val, err := goTypeToTs(route.ResponseType.Name, prefixForType)
+			if len(route.ResponseTypeName()) > 0 {
+				val, err := goTypeToTs(route.ResponseType, true)
 				if err != nil {
 					return "", err
 				}
+
 				responseGeneric = fmt.Sprintf("<%s>", val)
 			}
 			fmt.Fprintf(&builder, `return %s.%s%s(%s)`, caller, strings.ToLower(route.Method),
@@ -153,14 +101,18 @@ func genApi(api *spec.ApiSpec, caller string, prefixForType func(string) string)
 	return apis, nil
 }
 
-func paramsForRoute(route spec.Route, prefixForType func(string) string) string {
+func paramsForRoute(route spec.Route) string {
+	if route.RequestType == nil {
+		return ""
+	}
 	hasParams := pathHasParams(route)
 	hasBody := hasRequestBody(route)
-	rt, err := goTypeToTs(route.RequestType.Name, prefixForType)
+	rt, err := goTypeToTs(route.RequestType, true)
 	if err != nil {
 		fmt.Println(err.Error())
 		return ""
 	}
+
 	if hasParams && hasBody {
 		return fmt.Sprintf("params: %s, req: %s", rt+"Params", rt)
 	} else if hasParams {
@@ -173,7 +125,7 @@ func paramsForRoute(route spec.Route, prefixForType func(string) string) string
 
 func commentForRoute(route spec.Route) string {
 	var builder strings.Builder
-	comment, _ := apiutil.GetAnnotationValue(route.Annotations, "doc", "summary")
+	comment := route.JoinedDoc()
 	builder.WriteString("/**")
 	builder.WriteString("\n * @description " + comment)
 	hasParams := pathHasParams(route)
@@ -200,24 +152,35 @@ func callParamsForRoute(route spec.Route, group spec.Group) string {
 	} else if hasBody {
 		return fmt.Sprintf("%s, %s", pathForRoute(route, group), "req")
 	}
+
 	return pathForRoute(route, group)
 }
 
 func pathForRoute(route spec.Route, group spec.Group) string {
-	value, ok := apiutil.GetAnnotationValue(group.Annotations, "server", pathPrefix)
-	if !ok {
+	prefix := group.GetAnnotation("pathPrefix")
+	if len(prefix) == 0 {
 		return "\"" + route.Path + "\""
 	} else {
-		value = strings.TrimPrefix(value, `"`)
-		value = strings.TrimSuffix(value, `"`)
-		return fmt.Sprintf(`"%s/%s"`, value, strings.TrimPrefix(route.Path, "/"))
+		prefix = strings.TrimPrefix(prefix, `"`)
+		prefix = strings.TrimSuffix(prefix, `"`)
+		return fmt.Sprintf(`"%s/%s"`, prefix, strings.TrimPrefix(route.Path, "/"))
 	}
 }
 
 func pathHasParams(route spec.Route) bool {
-	return len(route.RequestType.Members) != len(route.RequestType.GetBodyMembers())
+	ds, ok := route.RequestType.(spec.DefineStruct)
+	if !ok {
+		return false
+	}
+
+	return len(ds.Members) != len(ds.GetBodyMembers())
 }
 
 func hasRequestBody(route spec.Route) bool {
-	return len(route.RequestType.Name) > 0 && len(route.RequestType.GetBodyMembers()) > 0
+	ds, ok := route.RequestType.(spec.DefineStruct)
+	if !ok {
+		return false
+	}
+
+	return len(route.RequestTypeName()) > 0 && len(ds.GetBodyMembers()) > 0
 }

+ 62 - 69
tools/goctl/api/tsgen/util.go

@@ -1,6 +1,7 @@
 package tsgen
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"strings"
@@ -10,9 +11,9 @@ import (
 	"github.com/tal-tech/go-zero/tools/goctl/util"
 )
 
-func writeProperty(writer io.Writer, member spec.Member, indent int, prefixForType func(string) string) error {
+func writeProperty(writer io.Writer, member spec.Member, indent int) error {
 	writeIndent(writer, indent)
-	ty, err := goTypeToTs(member.Type, prefixForType)
+	ty, err := goTypeToTs(member.Type, false)
 	if err != nil {
 		return err
 	}
@@ -45,68 +46,56 @@ func writeIndent(writer io.Writer, indent int) {
 	}
 }
 
-func goTypeToTs(tp string, prefixForType func(string) string) (string, error) {
-	if val, pri := primitiveType(tp); pri {
-		return val, nil
-	}
-	if tp == "[]byte" {
-		return "Blob", nil
-	} else if strings.HasPrefix(tp, "[][]") {
-		tys, err := apiutil.DecomposeType(tp)
-		if err != nil {
-			return "", err
-		}
-		if len(tys) == 0 {
-			return "", fmt.Errorf("%s tp parse error", tp)
-		}
-		innerType, err := goTypeToTs(tys[0], prefixForType)
-		if err != nil {
-			return "", err
-		}
-		return fmt.Sprintf("Array<Array<%s>>", innerType), nil
-	} else if strings.HasPrefix(tp, "[]") {
-		tys, err := apiutil.DecomposeType(tp)
-		if err != nil {
-			return "", err
-		}
-		if len(tys) == 0 {
-			return "", fmt.Errorf("%s tp parse error", tp)
-		}
-		innerType, err := goTypeToTs(tys[0], prefixForType)
-		if err != nil {
-			return "", err
+func goTypeToTs(tp spec.Type, fromPacket bool) (string, error) {
+	switch v := tp.(type) {
+	case spec.DefineStruct:
+		return addPrefix(tp, fromPacket), nil
+	case spec.PrimitiveType:
+		r, ok := primitiveType(tp.Name())
+		if !ok {
+			return "", errors.New("unsupported primitive type " + tp.Name())
 		}
-		return fmt.Sprintf("Array<%s>", innerType), nil
-	} else if strings.HasPrefix(tp, "map") {
-		tys, err := apiutil.DecomposeType(tp)
+
+		return r, nil
+	case spec.MapType:
+		valueType, err := goTypeToTs(v.Value, fromPacket)
 		if err != nil {
 			return "", err
 		}
-		if len(tys) != 2 {
-			return "", fmt.Errorf("%s tp parse error", tp)
+
+		return fmt.Sprintf("{ [key: string]: %s }", valueType), nil
+	case spec.ArrayType:
+		if tp.Name() == "[]byte" {
+			return "Blob", nil
 		}
-		innerType, err := goTypeToTs(tys[1], prefixForType)
+
+		valueType, err := goTypeToTs(v.Value, fromPacket)
 		if err != nil {
 			return "", err
 		}
-		return fmt.Sprintf("{ [key: string]: %s }", innerType), nil
+
+		return fmt.Sprintf("Array<%s>", valueType), nil
+	case spec.InterfaceType:
+		return "any", nil
+	case spec.PointerType:
+		return goTypeToTs(v.Type, fromPacket)
 	}
-	return addPrefixIfNeed(util.Title(tp), prefixForType), nil
+
+	return "", errors.New("unsupported type " + tp.Name())
 }
 
-func addPrefixIfNeed(tp string, prefixForType func(string) string) string {
-	if val, pri := primitiveType(tp); pri {
-		return val
+func addPrefix(tp spec.Type, fromPacket bool) string {
+	if fromPacket {
+		return packagePrefix + util.Title(tp.Name())
 	}
-	tp = strings.Replace(tp, "*", "", 1)
-	return prefixForType(tp) + util.Title(tp)
+	return util.Title(tp.Name())
 }
 
 func primitiveType(tp string) (string, bool) {
 	switch tp {
 	case "string":
 		return "string", true
-	case "int", "int8", "int32", "int64":
+	case "int", "int8", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
 		return "number", true
 	case "float", "float32", "float64":
 		return "number", true
@@ -120,52 +109,56 @@ func primitiveType(tp string) (string, bool) {
 	return "", false
 }
 
-func writeType(writer io.Writer, tp spec.Type, inlineType func(string) (*spec.Type, error), prefixForType func(string) string) error {
-	fmt.Fprintf(writer, "export interface %s {\n", util.Title(tp.Name))
-	if err := genMembers(writer, tp, false, inlineType, prefixForType); err != nil {
+func writeType(writer io.Writer, tp spec.Type) error {
+	fmt.Fprintf(writer, "export interface %s {\n", util.Title(tp.Name()))
+	if err := genMembers(writer, tp, false); err != nil {
 		return err
 	}
+
 	fmt.Fprintf(writer, "}\n")
-	err := genParamsTypesIfNeed(writer, tp, inlineType, prefixForType)
-	if err != nil {
-		return err
-	}
-	return nil
+	return genParamsTypesIfNeed(writer, tp)
 }
 
-func genParamsTypesIfNeed(writer io.Writer, tp spec.Type, inlineType func(string) (*spec.Type, error), prefixForType func(string) string) error {
-	members := tp.GetNonBodyMembers()
+func genParamsTypesIfNeed(writer io.Writer, tp spec.Type) error {
+	definedType, ok := tp.(spec.DefineStruct)
+	if !ok {
+		return errors.New("no members of type " + tp.Name())
+	}
+
+	members := definedType.GetNonBodyMembers()
 	if len(members) == 0 {
 		return nil
 	}
 	fmt.Fprintf(writer, "\n")
-	fmt.Fprintf(writer, "export interface %sParams {\n", util.Title(tp.Name))
-	if err := genMembers(writer, tp, true, inlineType, prefixForType); err != nil {
+	fmt.Fprintf(writer, "export interface %sParams {\n", util.Title(tp.Name()))
+	if err := genMembers(writer, tp, true); err != nil {
 		return err
 	}
+
 	fmt.Fprintf(writer, "}\n")
 	return nil
 }
 
-func genMembers(writer io.Writer, tp spec.Type, isParam bool, inlineType func(string) (*spec.Type, error), prefixForType func(string) string) error {
-	members := tp.GetBodyMembers()
+func genMembers(writer io.Writer, tp spec.Type, isParam bool) error {
+	definedType, ok := tp.(spec.DefineStruct)
+	if !ok {
+		return errors.New("no members of type " + tp.Name())
+	}
+
+	members := definedType.GetBodyMembers()
 	if isParam {
-		members = tp.GetNonBodyMembers()
+		members = definedType.GetNonBodyMembers()
 	}
 	for _, member := range members {
 		if member.IsInline {
-			// 获取inline类型的成员然后添加到type中
-			it, err := inlineType(strings.TrimPrefix(member.Type, "*"))
-			if err != nil {
-				return err
-			}
-			if err := genMembers(writer, *it, isParam, inlineType, prefixForType); err != nil {
+			if err := genMembers(writer, member.Type, isParam); err != nil {
 				return err
 			}
 			continue
 		}
-		if err := writeProperty(writer, member, 1, prefixForType); err != nil {
-			return apiutil.WrapErr(err, " type "+tp.Name)
+
+		if err := writeProperty(writer, member, 1); err != nil {
+			return apiutil.WrapErr(err, " type "+tp.Name())
 		}
 	}
 	return nil

+ 0 - 20
tools/goctl/api/util/annotation.go

@@ -1,20 +0,0 @@
-package util
-
-import (
-	"strings"
-
-	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-)
-
-func GetAnnotationValue(annos []spec.Annotation, key, field string) (string, bool) {
-	for _, anno := range annos {
-		if anno.Name == field && len(anno.Value) > 0 {
-			return anno.Value, true
-		}
-		if anno.Name == key {
-			value, ok := anno.Properties[field]
-			return strings.TrimSpace(value), ok
-		}
-	}
-	return "", false
-}

+ 23 - 3
tools/goctl/api/util/case.go

@@ -1,5 +1,10 @@
 package util
 
+import (
+	"strings"
+	"unicode"
+)
+
 func IsUpperCase(r rune) bool {
 	if r >= 'A' && r <= 'Z' {
 		return true
@@ -15,7 +20,7 @@ func IsLowerCase(r rune) bool {
 }
 
 func ToSnakeCase(s string) string {
-	out := []rune{}
+	var out []rune
 	for index, r := range s {
 		if index == 0 {
 			out = append(out, ToLowerCase(r))
@@ -77,7 +82,7 @@ func ToUpperCase(r rune) rune {
 }
 
 func ToLower(s string) string {
-	out := []rune{}
+	var out []rune
 	for _, r := range s {
 		out = append(out, ToLowerCase(r))
 	}
@@ -85,7 +90,7 @@ func ToLower(s string) string {
 }
 
 func ToUpper(s string) string {
-	out := []rune{}
+	var out []rune
 	for _, r := range s {
 		out = append(out, ToUpperCase(r))
 	}
@@ -105,3 +110,18 @@ func UpperFirst(s string) string {
 	}
 	return ToUpper(s[:1]) + s[1:]
 }
+
+func UnExport(text string) bool {
+	var flag bool
+	str := strings.Map(func(r rune) rune {
+		if flag {
+			return r
+		}
+		if unicode.IsLetter(r) {
+			flag = true
+			return unicode.ToLower(r)
+		}
+		return r
+	}, text)
+	return str == text
+}

+ 0 - 159
tools/goctl/api/util/types.go

@@ -1,159 +0,0 @@
-package util
-
-import (
-	"fmt"
-	"strings"
-
-	"github.com/tal-tech/go-zero/tools/goctl/api/spec"
-)
-
-func DecomposeType(t string) (result []string, err error) {
-	add := func(tp string) error {
-		ret, err := DecomposeType(tp)
-		if err != nil {
-			return err
-		}
-
-		result = append(result, ret...)
-		return nil
-	}
-	if strings.HasPrefix(t, "map") {
-		t = strings.ReplaceAll(t, "map", "")
-		if t[0] == '[' {
-			pos := strings.Index(t, "]")
-			if pos > 1 {
-				if err = add(t[1:pos]); err != nil {
-					return
-				}
-				if len(t) > pos+1 {
-					err = add(t[pos+1:])
-					return
-				}
-			}
-		}
-	} else if strings.HasPrefix(t, "[]") {
-		if len(t) > 2 {
-			err = add(t[2:])
-			return
-		}
-	} else if strings.HasPrefix(t, "*") {
-		err = add(t[1:])
-		return
-	} else {
-		result = append(result, t)
-		return
-	}
-
-	err = fmt.Errorf("bad type %q", t)
-	return
-}
-
-func GetAllTypes(api *spec.ApiSpec, route spec.Route) []spec.Type {
-	var rts []spec.Type
-	types := api.Types
-	getTypeRecursive(route.RequestType, types, &rts)
-	getTypeRecursive(route.ResponseType, types, &rts)
-	return rts
-}
-
-func GetLocalTypes(api *spec.ApiSpec, route spec.Route) []spec.Type {
-	sharedTypes := GetSharedTypes(api)
-	isSharedType := func(ty spec.Type) bool {
-		for _, item := range sharedTypes {
-			if item.Name == ty.Name {
-				return true
-			}
-		}
-		return false
-	}
-
-	var rts = GetAllTypes(api, route)
-
-	var result []spec.Type
-	for _, item := range rts {
-		if !isSharedType(item) {
-			result = append(result, item)
-		}
-	}
-	return result
-}
-
-func getTypeRecursive(ty spec.Type, allTypes []spec.Type, result *[]spec.Type) {
-	isCustomType := func(name string) (*spec.Type, bool) {
-		for _, item := range allTypes {
-			if item.Name == name {
-				return &item, true
-			}
-		}
-		return nil, false
-	}
-	if len(ty.Name) > 0 {
-		*result = append(*result, ty)
-	}
-	for _, member := range ty.Members {
-		decomposedItems, _ := DecomposeType(member.Type)
-		if len(decomposedItems) == 0 {
-			continue
-		}
-		var customTypes []spec.Type
-		for _, item := range decomposedItems {
-			c, e := isCustomType(item)
-			if e {
-				customTypes = append(customTypes, *c)
-			}
-		}
-		for _, ty := range customTypes {
-			hasAppend := false
-			for _, item := range *result {
-				if ty.Name == item.Name {
-					hasAppend = true
-					break
-				}
-
-			}
-			if !hasAppend {
-				getTypeRecursive(ty, allTypes, result)
-			}
-		}
-	}
-}
-
-func GetSharedTypes(api *spec.ApiSpec) []spec.Type {
-	types := api.Types
-	var result []spec.Type
-	var container []spec.Type
-	hasInclude := func(all []spec.Type, ty spec.Type) bool {
-		for _, item := range all {
-			if item.Name == ty.Name {
-				return true
-			}
-		}
-		return false
-	}
-	for _, route := range api.Service.Routes() {
-		var rts []spec.Type
-		getTypeRecursive(route.RequestType, types, &rts)
-		getTypeRecursive(route.ResponseType, types, &rts)
-		for _, item := range rts {
-			if len(item.Name) == 0 {
-				continue
-			}
-			if hasInclude(container, item) {
-				hasAppend := false
-				for _, r := range result {
-					if item.Name == r.Name {
-						hasAppend = true
-						break
-					}
-
-				}
-				if !hasAppend {
-					result = append(result, item)
-				}
-			} else {
-				container = append(container, item)
-			}
-		}
-	}
-	return result
-}

+ 1 - 5
tools/goctl/api/validate/validate.go

@@ -16,11 +16,7 @@ func GoValidateApi(c *cli.Context) error {
 		return errors.New("missing -api")
 	}
 
-	p, err := parser.NewParser(apiFile)
-	if err != nil {
-		return err
-	}
-	_, err = p.Parse()
+	_, err := parser.Parse(apiFile)
 	if err == nil {
 		fmt.Println(aurora.Green("api format ok"))
 	}

+ 6 - 0
tools/goctl/goctl.go

@@ -23,12 +23,18 @@ import (
 	"github.com/tal-tech/go-zero/tools/goctl/plugin"
 	rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/cli"
 	"github.com/tal-tech/go-zero/tools/goctl/tpl"
+	"github.com/tal-tech/go-zero/tools/goctl/upgrade"
 	"github.com/urfave/cli"
 )
 
 var (
 	BuildVersion = "1.1.3"
 	commands     = []cli.Command{
+		{
+			Name:   "upgrade",
+			Usage:  "upgrade goctl to latest version",
+			Action: upgrade.Upgrade,
+		},
 		{
 			Name:  "api",
 			Usage: "generate api related files",

+ 3 - 9
tools/goctl/plugin/plugin.go

@@ -20,9 +20,7 @@ import (
 	"github.com/urfave/cli"
 )
 
-const (
-	pluginArg = "_plugin"
-)
+const pluginArg = "_plugin"
 
 type Plugin struct {
 	Api         *spec.ApiSpec
@@ -74,12 +72,7 @@ func prepareArgs(c *cli.Context) ([]byte, error) {
 
 	var transferData Plugin
 	if len(apiPath) > 0 && util.FileExists(apiPath) {
-		p, err := parser.NewParser(apiPath)
-		if err != nil {
-			return nil, err
-		}
-
-		api, err := p.Parse()
+		api, err := parser.Parse(apiPath)
 		if err != nil {
 			return nil, err
 		}
@@ -183,6 +176,7 @@ func getPluginAndArgs(arg string) (string, string) {
 
 	return trimQuote(arg[:i]), trimQuote(arg[i+1:])
 }
+
 func trimQuote(in string) string {
 	in = strings.Trim(in, `"`)
 	in = strings.Trim(in, `'`)

+ 18 - 0
tools/goctl/upgrade/upgrade.go

@@ -0,0 +1,18 @@
+package upgrade
+
+import (
+	"fmt"
+
+	"github.com/tal-tech/go-zero/tools/goctl/rpc/execx"
+	"github.com/urfave/cli"
+)
+
+func Upgrade(_ *cli.Context) error {
+	info, err := execx.Run("GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl", "")
+	if err != nil {
+		return err
+	}
+
+	fmt.Print(info)
+	return nil
+}