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>
This commit is contained in:
kingxt
2021-01-11 15:10:51 +08:00
committed by GitHub
parent b0ccfb8eb4
commit ee19fb736b
88 changed files with 13641 additions and 2458 deletions

View File

@@ -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, " ")
}

View File

@@ -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)
}

View File

@@ -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"])
}

View File

@@ -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
}

View File

@@ -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
;

View File

@@ -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)?)))+;

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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()))
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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{}
}

View File

@@ -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)
}

View File

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

View File

@@ -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
}

View File

@@ -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")},
},
},
},
},
},
},
},
},
}))
})
}

View File

@@ -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)
}

View File

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

View File

@@ -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)
}

View File

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

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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"
`)
}

View File

@@ -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)
})
}
}

View File

@@ -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)
})
}

View File

@@ -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)
})
}

View File

@@ -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"),
},
},
},
},
}))
})
}

View File

@@ -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"),
}))
})
}

View File

@@ -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)
})
}

View File

@@ -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
}

View File

@@ -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)
spec := new(spec.ApiSpec)
p := parser{ast: ast, spec: spec}
err = p.convert2Spec()
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))
}
content, err := ioutil.ReadFile(path)
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))
}
}
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
}
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))
}
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,
}
}
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) 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 nil, errors.New("import api file not exist: " + item)
return err
}
importStruct, err := ParseApi(string(content))
group.Routes = append(group.Routes, route)
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
}
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 nil, err
return err
}
if len(importStruct.Imports) > 0 {
return nil, errors.New("import api should not import another api file recursive")
route.RequestType = *tp
}
}
if route.ResponseType != nil {
switch route.ResponseType.(type) {
case spec.DefineStruct:
tp, err := p.findDefinedType(route.ResponseType.Name())
if err != nil {
return err
}
apiStruct.Type += "\n" + importStruct.Type
apiStruct.Service += "\n" + importStruct.Service
}
}
if len(strings.TrimSpace(apiStruct.Service)) == 0 {
return nil, errors.New("api has no service defined")
}
var buffer = new(bytes.Buffer)
buffer.WriteString(apiStruct.Service)
return &Parser{
r: bufio.NewReader(buffer),
api: apiStruct,
}, nil
}
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
}
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)
}
if err != nil {
return nil, fmt.Errorf("near line: %d, %s", lineNumber, err.Error())
}
if st == nil {
return api, p.validate(api)
route.ResponseType = *tp
}
}
return nil
}

View File

@@ -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`是否为一个合法值。
>
> VALUEkey对应的值可以为单行的除'\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
)
```
eg3key-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|标志请求体是一个formPOST方法时或者一个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`是必须是小写。
>
> bodyapi请求体语法定义必须要由()包裹的可选的ID值
>
> replyBodyapi响应体语法定义必须由()包裹的struct、~~array(向前兼容处理后续可能会废弃强烈推荐以struct包裹不要直接用array作为响应体)~~
>
> kvLit 同info key-value
>
> serviceName: 可以有多个'-'join的ID值
>
> pathapi请求路径必须以'/'或者'/:'开头,切不能以'/'结尾中间可包含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
}
```

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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, ""
}

View File

@@ -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"
)