mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-10 00:20:00 +08:00
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:
@@ -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, " ")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"])
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
type (
|
||||
entity struct {
|
||||
state *baseState
|
||||
api *spec.ApiSpec
|
||||
parser entityParser
|
||||
}
|
||||
|
||||
entityParser interface {
|
||||
parseLine(line string, api *spec.ApiSpec, annos []spec.Annotation) error
|
||||
setEntityName(name string)
|
||||
}
|
||||
)
|
||||
|
||||
func newEntity(state *baseState, api *spec.ApiSpec, parser entityParser) entity {
|
||||
return entity{
|
||||
state: state,
|
||||
api: api,
|
||||
parser: parser,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *entity) process() error {
|
||||
line, err := s.state.readLineSkipComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
return fmt.Errorf("invalid type definition for %q",
|
||||
strings.TrimSpace(strings.Trim(string(line), "{")))
|
||||
}
|
||||
|
||||
if len(fields) == 2 {
|
||||
if fields[1] != leftBrace {
|
||||
return fmt.Errorf("bad string %q after type", fields[1])
|
||||
}
|
||||
} else if len(fields) == 3 {
|
||||
if fields[1] != typeStruct {
|
||||
return fmt.Errorf("bad string %q after type", fields[1])
|
||||
}
|
||||
if fields[2] != leftBrace {
|
||||
return fmt.Errorf("bad string %q after type", fields[2])
|
||||
}
|
||||
}
|
||||
|
||||
s.parser.setEntityName(fields[0])
|
||||
|
||||
var annos []spec.Annotation
|
||||
memberLoop:
|
||||
for {
|
||||
ch, err := s.state.readSkipComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var annoName string
|
||||
var builder strings.Builder
|
||||
switch {
|
||||
case ch == at:
|
||||
annotationLoop:
|
||||
for {
|
||||
next, err := s.state.readSkipComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case isSpace(next):
|
||||
if builder.Len() > 0 && annoName == "" {
|
||||
annoName = builder.String()
|
||||
builder.Reset()
|
||||
}
|
||||
case isNewline(next):
|
||||
if builder.Len() == 0 {
|
||||
return errors.New("invalid annotation format")
|
||||
}
|
||||
|
||||
if len(annoName) > 0 {
|
||||
value := builder.String()
|
||||
if value != string(leftParenthesis) {
|
||||
builder.Reset()
|
||||
annos = append(annos, spec.Annotation{
|
||||
Name: annoName,
|
||||
Value: value,
|
||||
})
|
||||
annoName = ""
|
||||
break annotationLoop
|
||||
}
|
||||
}
|
||||
case next == leftParenthesis:
|
||||
if builder.Len() == 0 {
|
||||
return errors.New("invalid annotation format")
|
||||
}
|
||||
annoName = builder.String()
|
||||
builder.Reset()
|
||||
if err := s.state.unread(); err != nil {
|
||||
return err
|
||||
}
|
||||
attrs, err := s.state.parseProperties()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
annos = append(annos, spec.Annotation{
|
||||
Name: annoName,
|
||||
Properties: attrs,
|
||||
})
|
||||
annoName = ""
|
||||
break annotationLoop
|
||||
default:
|
||||
builder.WriteRune(next)
|
||||
}
|
||||
}
|
||||
case ch == rightBrace:
|
||||
break memberLoop
|
||||
case isLetterDigit(ch):
|
||||
if err := s.state.unread(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var line string
|
||||
line, err = s.state.readLineSkipComment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
if err := s.parser.parseLine(line, s.api, annos); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
annos = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
46
tools/goctl/api/parser/g4/ApiLexer.g4
Normal file
46
tools/goctl/api/parser/g4/ApiLexer.g4
Normal 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
|
||||
;
|
||||
73
tools/goctl/api/parser/g4/ApiParser.g4
Normal file
73
tools/goctl/api/parser/g4/ApiParser.g4
Normal 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)?)))+;
|
||||
251
tools/goctl/api/parser/g4/ast/api.go
Normal file
251
tools/goctl/api/parser/g4/ast/api.go
Normal 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
|
||||
}
|
||||
405
tools/goctl/api/parser/g4/ast/apiparser.go
Normal file
405
tools/goctl/api/parser/g4/ast/apiparser.go
Normal 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
|
||||
}
|
||||
}
|
||||
325
tools/goctl/api/parser/g4/ast/ast.go
Normal file
325
tools/goctl/api/parser/g4/ast/ast.go
Normal 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()))
|
||||
}
|
||||
}
|
||||
96
tools/goctl/api/parser/g4/ast/import.go
Normal file
96
tools/goctl/api/parser/g4/ast/import.go
Normal 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
|
||||
}
|
||||
67
tools/goctl/api/parser/g4/ast/info.go
Normal file
67
tools/goctl/api/parser/g4/ast/info.go
Normal 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
|
||||
}
|
||||
79
tools/goctl/api/parser/g4/ast/kv.go
Normal file
79
tools/goctl/api/parser/g4/ast/kv.go
Normal 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
|
||||
}
|
||||
5
tools/goctl/api/parser/g4/ast/placeholder.go
Normal file
5
tools/goctl/api/parser/g4/ast/placeholder.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package ast
|
||||
|
||||
var Holder PlaceHolder
|
||||
|
||||
type PlaceHolder struct{}
|
||||
603
tools/goctl/api/parser/g4/ast/service.go
Normal file
603
tools/goctl/api/parser/g4/ast/service.go
Normal 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
|
||||
}
|
||||
58
tools/goctl/api/parser/g4/ast/syntax.go
Normal file
58
tools/goctl/api/parser/g4/ast/syntax.go
Normal 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
|
||||
}
|
||||
677
tools/goctl/api/parser/g4/ast/type.go
Normal file
677
tools/goctl/api/parser/g4/ast/type.go
Normal 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
|
||||
}
|
||||
156
tools/goctl/api/parser/g4/gen/api/apiparser_base_visitor.go
Normal file
156
tools/goctl/api/parser/g4/gen/api/apiparser_base_visitor.go
Normal 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)
|
||||
}
|
||||
234
tools/goctl/api/parser/g4/gen/api/apiparser_lexer.go
Normal file
234
tools/goctl/api/parser/g4/gen/api/apiparser_lexer.go
Normal 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
|
||||
5688
tools/goctl/api/parser/g4/gen/api/apiparser_parser.go
Normal file
5688
tools/goctl/api/parser/g4/gen/api/apiparser_parser.go
Normal file
File diff suppressed because it is too large
Load Diff
120
tools/goctl/api/parser/g4/gen/api/apiparser_visitor.go
Normal file
120
tools/goctl/api/parser/g4/gen/api/apiparser_visitor.go
Normal 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{}
|
||||
}
|
||||
222
tools/goctl/api/parser/g4/gen/api/baseparser.go
Normal file
222
tools/goctl/api/parser/g4/gen/api/baseparser.go
Normal 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)
|
||||
}
|
||||
11
tools/goctl/api/parser/g4/gen/api/baseparser_test.go
Normal file
11
tools/goctl/api/parser/g4/gen/api/baseparser_test.go
Normal 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))
|
||||
}
|
||||
99
tools/goctl/api/parser/g4/test.api
Normal file
99
tools/goctl/api/parser/g4/test.api
Normal 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
|
||||
}
|
||||
369
tools/goctl/api/parser/g4/test/apiparser_test.go
Normal file
369
tools/goctl/api/parser/g4/test/apiparser_test.go
Normal 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")},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
0
tools/goctl/api/parser/g4/test/apis/empty.api
Normal file
0
tools/goctl/api/parser/g4/test/apis/empty.api
Normal file
72
tools/goctl/api/parser/g4/test/apis/example.api
Normal file
72
tools/goctl/api/parser/g4/test/apis/example.api
Normal 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)
|
||||
}
|
||||
6
tools/goctl/api/parser/g4/test/apis/info.api
Normal file
6
tools/goctl/api/parser/g4/test/apis/info.api
Normal file
@@ -0,0 +1,6 @@
|
||||
info(
|
||||
author: songmeizi
|
||||
desc: "the sample of
|
||||
info"
|
||||
date: "2020-01-06"
|
||||
)
|
||||
29
tools/goctl/api/parser/g4/test/apis/service.api
Normal file
29
tools/goctl/api/parser/g4/test/apis/service.api
Normal 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)
|
||||
}
|
||||
1
tools/goctl/api/parser/g4/test/apis/syntax.api
Normal file
1
tools/goctl/api/parser/g4/test/apis/syntax.api
Normal file
@@ -0,0 +1 @@
|
||||
syntax = "v1"
|
||||
111
tools/goctl/api/parser/g4/test/apis/test.api
Normal file
111
tools/goctl/api/parser/g4/test/apis/test.api
Normal 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)
|
||||
}
|
||||
23
tools/goctl/api/parser/g4/test/apis/types.api
Normal file
23
tools/goctl/api/parser/g4/test/apis/types.api
Normal 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
|
||||
}
|
||||
|
||||
457
tools/goctl/api/parser/g4/test/ast_test.go
Normal file
457
tools/goctl/api/parser/g4/test/ast_test.go
Normal 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"
|
||||
`)
|
||||
}
|
||||
25
tools/goctl/api/parser/g4/test/grammar_test.go
Normal file
25
tools/goctl/api/parser/g4/test/grammar_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
143
tools/goctl/api/parser/g4/test/import_test.go
Normal file
143
tools/goctl/api/parser/g4/test/import_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
186
tools/goctl/api/parser/g4/test/info_test.go
Normal file
186
tools/goctl/api/parser/g4/test/info_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
686
tools/goctl/api/parser/g4/test/service_test.go
Normal file
686
tools/goctl/api/parser/g4/test/service_test.go
Normal 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"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
75
tools/goctl/api/parser/g4/test/syntax_test.go
Normal file
75
tools/goctl/api/parser/g4/test/syntax_test.go
Normal 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"),
|
||||
}))
|
||||
})
|
||||
}
|
||||
467
tools/goctl/api/parser/g4/test/type_test.go
Normal file
467
tools/goctl/api/parser/g4/test/type_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
682
tools/goctl/api/parser/readme.md
Normal file
682
tools/goctl/api/parser/readme.md
Normal 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`是否为一个合法值。
|
||||
>
|
||||
> VALUE:key对应的值,可以为单行的除'\r','\n','/'后的任意字符,多行请以""包裹,不过强烈建议所有都以""包裹
|
||||
>
|
||||
|
||||
**正确语法示例** ✅
|
||||
|
||||
eg1:不规范写法
|
||||
|
||||
``` api
|
||||
info(
|
||||
foo: foo value
|
||||
bar:"bar value"
|
||||
desc:"long long long long
|
||||
long long text"
|
||||
)
|
||||
```
|
||||
|
||||
eg2:规范写法(推荐)
|
||||
|
||||
``` api
|
||||
info(
|
||||
foo: "foo value"
|
||||
bar: "bar value"
|
||||
desc: "long long long long long long text"
|
||||
)
|
||||
```
|
||||
|
||||
**错误语法示例** ❌
|
||||
|
||||
eg1:没有key-value内容
|
||||
|
||||
``` api
|
||||
info()
|
||||
```
|
||||
|
||||
eg2:不包含冒号
|
||||
|
||||
``` api
|
||||
info(
|
||||
foo value
|
||||
)
|
||||
```
|
||||
|
||||
eg3:key-value没有换行
|
||||
|
||||
``` api
|
||||
info(foo:"value")
|
||||
```
|
||||
|
||||
eg4:没有key
|
||||
|
||||
``` api
|
||||
info(
|
||||
: "value"
|
||||
)
|
||||
```
|
||||
|
||||
eg5:非法的key
|
||||
|
||||
``` api
|
||||
info(
|
||||
12: "value"
|
||||
)
|
||||
```
|
||||
|
||||
eg6:移除旧版本多行语法
|
||||
|
||||
``` api
|
||||
info(
|
||||
foo: >
|
||||
some text
|
||||
<
|
||||
)
|
||||
```
|
||||
|
||||
## type语法块
|
||||
|
||||
在api服务中,我们需要用到一个结构体(类)来作为请求体,响应体的载体,因此我们需要声明一些结构体来完成这件事情, type语法块由golang的type演变而来,当然也保留着一些golang type的特性,沿用golang特性有:
|
||||
|
||||
* 保留了golang内置数据类型`bool`,`int`,`int8`,`int16`,`int32`,`int64`,`uint`,`uint8`,`uint16`,`uint32`,`uint64`,`uintptr`
|
||||
,`float32`,`float64`,`complex64`,`complex128`,`string`,`byte`,`rune`,
|
||||
* 兼容golang struct风格声明
|
||||
* 保留golang关键字
|
||||
|
||||
> ### 警告 ⚠️
|
||||
> * 不支持alias
|
||||
> * 不支持time.Time数据类型
|
||||
> * 结构体名称、字段名称、不能为golang关键字
|
||||
|
||||
**语法定义**
|
||||
> 由于其和golang相似,因此不做详细说明,具体语法定义请在[ApiParser.g4](g4/ApiParser.g4)中查看typeSpec定义。
|
||||
|
||||
**语法说明**
|
||||
|
||||
> 参考golang写法
|
||||
|
||||
**正确语法示例** ✅
|
||||
|
||||
eg1:不规范写法
|
||||
|
||||
``` api
|
||||
type Foo struct{
|
||||
Id int `path:"id"` // ①
|
||||
Foo int `json:"foo"`
|
||||
}
|
||||
|
||||
type Bar struct{
|
||||
// 非导出型字段
|
||||
bar int `form:"bar"`
|
||||
}
|
||||
|
||||
type(
|
||||
// 非导出型结构体
|
||||
fooBar struct{
|
||||
FooBar int
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
eg2:规范写法(推荐)
|
||||
|
||||
``` api
|
||||
type Foo{
|
||||
Id int `path:"id"`
|
||||
Foo int `json:"foo"`
|
||||
}
|
||||
|
||||
type Bar{
|
||||
Bar int `form:"bar"`
|
||||
}
|
||||
|
||||
type(
|
||||
FooBar{
|
||||
FooBar int
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**错误语法示例** ❌
|
||||
|
||||
eg
|
||||
|
||||
``` api
|
||||
type Gender int // 不支持
|
||||
|
||||
// 非struct token
|
||||
type Foo structure{
|
||||
CreateTime time.Time // 不支持time.Time
|
||||
}
|
||||
|
||||
// golang关键字 var
|
||||
type var{}
|
||||
|
||||
type Foo{
|
||||
// golang关键字 interface
|
||||
Foo interface
|
||||
}
|
||||
|
||||
|
||||
type Foo{
|
||||
foo int
|
||||
// map key必须要golang内置数据类型
|
||||
m map[Bar]string
|
||||
}
|
||||
```
|
||||
|
||||
**① tag说明**
|
||||
> tag定义和golang中json tag语法一样,除了json tag外,go-zero还提供了另外一些tag来实现对字段的描述,
|
||||
> 详情见下表。
|
||||
|
||||
* tag表
|
||||
|
||||
|tag key |描述 |提供方 |有效范围 |示例 |
|
||||
|:--- |:--- |:--- |:--- |:--- |
|
||||
|json|json序列化tag|golang|request、response|`json:"fooo"`|
|
||||
|path|路由path,如`/foo/:id`|go-zero|request|`path:"id"`|
|
||||
|form|标志请求体是一个form(POST方法时)或者一个query(GET方法时`/search?name=keyword`)|go-zero|request|`form:"name"`|
|
||||
|
||||
* tag修饰符
|
||||
> 常见参数校验描述
|
||||
|
||||
|tag key |描述 |提供方 |有效范围 |示例 |
|
||||
|:--- |:--- |:--- |:--- |:--- |
|
||||
|optional|定义当前字段为可选参数|go-zero|request|`json:"name,optional"`|
|
||||
|options|定义当前字段的枚举值,多个以竖线②隔开|go-zero|request|`json:"gender,options=male"`|
|
||||
|default|定义当前字段默认值|go-zero|request|`json:"gender,default=male"`|
|
||||
|range|定义当前字段数值范围|go-zero|request|`json:"age,range=[0:120]"`|
|
||||
|
||||
② 竖线:|
|
||||
> ### 温馨提示
|
||||
> tag修饰符需要在tag value后以引文逗号,隔开
|
||||
|
||||
## service语法块
|
||||
|
||||
service语法块用于定义api服务,包含服务名称,服务metadata,中间件声明,路由,handler等。
|
||||
|
||||
> ### 警告 ⚠️
|
||||
> * main api和被import的api服务名称必须一致,不能出现服务名称歧义。
|
||||
> * handler名称不能重复
|
||||
> * 路由(请求方法+请求path)名称不能重复
|
||||
> * 请求体必须声明为普通(非指针)struct,响应体做了一些向前兼容处理,详请见下文说明
|
||||
>
|
||||
|
||||
**语法定义**
|
||||
|
||||
``` antlrv4
|
||||
serviceSpec: atServer? serviceApi;
|
||||
atServer: '@server' lp='(' kvLit+ rp=')';
|
||||
serviceApi: {match(p,"service")}serviceToken=ID serviceName lbrace='{' serviceRoute* rbrace='}';
|
||||
serviceRoute: atDoc? (atServer|atHandler) route;
|
||||
atDoc: '@doc' lp='('? ((kvLit+)|STRING) rp=')'?;
|
||||
atHandler: '@handler' ID;
|
||||
route: {checkHttpMethod(p)}httpMethod=ID path request=body? returnToken=ID? response=replybody?;
|
||||
body: lp='(' (ID)? rp=')';
|
||||
replybody: lp='(' dataType? rp=')';
|
||||
// kv
|
||||
kvLit: key=ID {checkKeyValue(p)}value=LINE_VALUE;
|
||||
|
||||
serviceName: (ID '-'?)+;
|
||||
path: (('/' (ID ('-' ID)*))|('/:' (ID ('-' ID)?)))+;
|
||||
```
|
||||
|
||||
**语法说明**
|
||||
|
||||
> serviceSpec:包含了一个可选语法块`atServer`和`serviceApi`语法块,其遵循序列模式(编写service必须要按照顺序,否则会解析出错)
|
||||
>
|
||||
> atServer: 可选语法块,定义key-value结构的server metadata,'@server'表示这一个server语法块的开始,其可以用于描述serviceApi或者route语法块,其用于描述不同语法块时有一些特殊关键key
|
||||
> 需要值得注意,见 **atServer关键key描述说明**。
|
||||
>
|
||||
> serviceApi:包含了1到多个`serviceRoute`语法块
|
||||
>
|
||||
> serviceRoute:按照序列模式包含了`atDoc`,handler和`route`
|
||||
>
|
||||
> atDoc:可选语法块,一个路由的key-value描述,其在解析后会传递到spec.Spec结构体,如果不关心传递到spec.Spec,
|
||||
> 推荐用单行注释替代。
|
||||
>
|
||||
> handler:是对路由的handler层描述,可以通过atServer指定`handler` key来指定handler名称,
|
||||
> 也可以直接用atHandler语法块来定义handler名称
|
||||
>
|
||||
> atHandler:'@handler' 固定token,后接一个遵循正则`[_a-zA-Z][a-zA-Z_-]*`)的值,用于声明一个handler名称
|
||||
>
|
||||
> route:路由,有`httpMethod`、`path`、可选`request`、可选`response`组成,`httpMethod`是必须是小写。
|
||||
>
|
||||
> body:api请求体语法定义,必须要由()包裹的可选的ID值
|
||||
>
|
||||
> replyBody:api响应体语法定义,必须由()包裹的struct、~~array(向前兼容处理,后续可能会废弃,强烈推荐以struct包裹,不要直接用array作为响应体)~~
|
||||
>
|
||||
> kvLit: 同info key-value
|
||||
>
|
||||
> serviceName: 可以有多个'-'join的ID值
|
||||
>
|
||||
> path:api请求路径,必须以'/'或者'/:'开头,切不能以'/'结尾,中间可包含ID或者多个以'-'join的ID字符串
|
||||
|
||||
**atServer关键key描述说明**
|
||||
|
||||
修饰service时
|
||||
|
||||
|key|描述|示例|
|
||||
|:---|:---|:---|
|
||||
|jwt|声明当前service下所有路由需要jwt鉴权,且会自动生成包含jwt逻辑的代码|`jwt: Auth`|
|
||||
|group|声明当前service或者路由文件分组|`group: login`|
|
||||
|middleware|声明当前service需要开启中间件|`middleware: AuthMiddleware`|
|
||||
|
||||
修饰route时
|
||||
|
||||
|key|描述|示例|
|
||||
|:---|:---|:---|
|
||||
|handler|声明一个handler|-|
|
||||
|
||||
**正确语法示例** ✅
|
||||
|
||||
eg1:不规范写法
|
||||
|
||||
``` api
|
||||
@server(
|
||||
jwt: Auth
|
||||
group: foo
|
||||
middleware: AuthMiddleware
|
||||
)
|
||||
service foo-api{
|
||||
@doc(
|
||||
summary: foo
|
||||
)
|
||||
@server(
|
||||
handler: foo
|
||||
)
|
||||
// 非导出型body
|
||||
post /foo/:id (foo) returns (bar)
|
||||
|
||||
@doc "bar"
|
||||
@handler bar
|
||||
post /bar returns ([]int)// 不推荐数组作为响应体
|
||||
|
||||
@handler fooBar
|
||||
post /foo/bar (Foo) returns // 可以省略'returns'
|
||||
}
|
||||
```
|
||||
|
||||
eg2:规范写法(推荐)
|
||||
|
||||
``` api
|
||||
@server(
|
||||
jwt: Auth
|
||||
group: foo
|
||||
middleware: AuthMiddleware
|
||||
)
|
||||
service foo-api{
|
||||
@doc "foo"
|
||||
@handler: foo
|
||||
post /foo/:id (Foo) returns (Bar)
|
||||
}
|
||||
|
||||
service foo-api{
|
||||
@handler ping
|
||||
get /ping
|
||||
|
||||
@doc "foo"
|
||||
@handler: bar
|
||||
post /bar/:id (Foo)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**错误语法示例** ❌
|
||||
|
||||
``` api
|
||||
// 不支持空的server语法块
|
||||
@server(
|
||||
)
|
||||
// 不支持空的service语法块
|
||||
service foo-api{
|
||||
}
|
||||
|
||||
service foo-api{
|
||||
@doc kkkk // 简版doc必须用英文双引号引起来
|
||||
@handler foo
|
||||
post /foo
|
||||
|
||||
@handler foo // 重复的handler
|
||||
post /bar
|
||||
|
||||
@handler fooBar
|
||||
post /bar // 重复的路由
|
||||
|
||||
// @handler和@doc顺序错误
|
||||
@handler someHandler
|
||||
@doc "some doc"
|
||||
post /some/path
|
||||
|
||||
// handler缺失
|
||||
post /some/path/:id
|
||||
|
||||
@handler reqTest
|
||||
post /foo/req (*Foo) // 不支持除普通结构体外的其他数据类型作为请求体
|
||||
|
||||
@handler replyTest
|
||||
post /foo/reply returns (*Foo) // 不支持除普通结构体、数组(向前兼容,后续考虑废弃)外的其他数据类型作为响应体
|
||||
}
|
||||
```
|
||||
|
||||
## 隐藏通道
|
||||
|
||||
隐藏通道目前主要为空百符号,换行符号以及注释,这里我们只说注释,因为空白符号和换行符号我们目前拿来也无用。
|
||||
|
||||
### 单行注释
|
||||
|
||||
**语法定义**
|
||||
|
||||
``` antlrv4
|
||||
'//' ~[\r\n]*
|
||||
```
|
||||
|
||||
**语法说明**
|
||||
由语法定义可知道,单行注释必须要以`//`开头,内容为不能包含换行符
|
||||
|
||||
**正确语法示例** ✅
|
||||
|
||||
``` api
|
||||
// doc
|
||||
// comment
|
||||
```
|
||||
|
||||
**错误语法示例** ❌
|
||||
|
||||
``` api
|
||||
// break
|
||||
line comments
|
||||
```
|
||||
|
||||
### java风格文档注释
|
||||
|
||||
**语法定义**
|
||||
|
||||
``` antlrv4
|
||||
'/*' .*? '*/'
|
||||
```
|
||||
|
||||
**语法说明**
|
||||
|
||||
由语法定义可知道,单行注释必须要以`/*`开头,`*/`结尾的任意字符。
|
||||
|
||||
**正确语法示例** ✅
|
||||
|
||||
``` api
|
||||
/**
|
||||
* java-style doc
|
||||
*/
|
||||
```
|
||||
|
||||
**错误语法示例** ❌
|
||||
|
||||
``` api
|
||||
/*
|
||||
* java-style doc */
|
||||
*/
|
||||
```
|
||||
|
||||
## Doc&Comment
|
||||
|
||||
如果想获取某一个元素的doc或者comment开发人员需要怎么定义?
|
||||
|
||||
**Doc**
|
||||
> 我们规定上一个语法块(非隐藏通道内容)的行数line+1到当前语法块第一个元素前的所有注释(当行,或者多行)均为doc, 且保留了`//`、`/*`、`*/`原始标记。
|
||||
|
||||
**Comment**
|
||||
> 我们规定当前语法块最后一个元素所在行开始的一个注释块(当行,或者多行)为comment 且保留了`//`、`/*`、`*/`原始标记。
|
||||
|
||||
语法块Doc和Comment的支持情况
|
||||
|
||||
|语法块|parent语法块|Doc|Comment|
|
||||
|:---|:---|:---|:---|
|
||||
|syntaxLit|api|✅|✅|
|
||||
|kvLit|infoSpec|✅|✅|
|
||||
|importLit|importSpec|✅|✅|
|
||||
|typeLit|api|✅|❌|
|
||||
|typeLit|typeBlock|✅|❌|
|
||||
|field|typeLit|✅|✅|
|
||||
|key-value|atServer|✅|✅|
|
||||
|atHandler|serviceRoute|✅|✅|
|
||||
|route|serviceRoute|✅|✅|
|
||||
|
||||
以下为对应语法块解析后细带doc和comment的写法
|
||||
``` api
|
||||
// syntaxLit doc
|
||||
syntax = "v1" // syntaxLit commnet
|
||||
|
||||
info(
|
||||
// kvLit doc
|
||||
author: songmeizi // kvLit comment
|
||||
)
|
||||
|
||||
// typeLit doc
|
||||
type Foo {}
|
||||
|
||||
type(
|
||||
// typeLit doc
|
||||
Bar{}
|
||||
|
||||
FooBar{
|
||||
// filed doc
|
||||
Name int // filed comment
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
/**
|
||||
* kvLit doc
|
||||
* 开启jwt鉴权
|
||||
*/
|
||||
jwt: Auth /**kvLit comment*/
|
||||
)
|
||||
service foo-api{
|
||||
// atHandler doc
|
||||
@handler foo //atHandler comment
|
||||
|
||||
/*
|
||||
* route doc
|
||||
* post请求
|
||||
* path为 /foo
|
||||
* 请求体:Foo
|
||||
* 响应体:Foo
|
||||
*/
|
||||
post /foo (Foo) returns (Foo) // route comment
|
||||
}
|
||||
```
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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, ""
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
Reference in New Issue
Block a user