123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- 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 provides api parsing capabilities
- Parser struct {
- linePrefix string
- debug bool
- log console.Console
- antlr.DefaultErrorListener
- }
- // ParserOption defines an function with argument Parser
- ParserOption func(p *Parser)
- )
- // NewParser creates an instance for 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 {
- err := p.nestedApiCheck(mainApi, nestedApi)
- if err != nil {
- return err
- }
- 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() {
- 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
- err = p.duplicateRouteCheck(nestedApi, mainHandlerMap, mainRouteMap)
- if err != nil {
- return err
- }
- // 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) duplicateRouteCheck(nestedApi *Api, mainHandlerMap map[string]PlaceHolder, mainRouteMap map[string]PlaceHolder) error {
- 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())
- }
- }
- }
- return nil
- }
- func (p *Parser) nestedApiCheck(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())
- }
- }
- }
- 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
- err := p.checkTypes(apiItem, linePrefix, types)
- if err != nil {
- return err
- }
- err = p.checkServices(apiItem, types, linePrefix)
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (p *Parser) checkServices(apiItem *Api, types map[string]TypeExpr, linePrefix string) error {
- for _, service := range apiItem.Service {
- for _, each := range service.ServiceApi.ServiceRoute {
- route := each.Route
- err := p.checkRequestBody(route, types, linePrefix)
- if err != nil {
- return err
- }
- 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) checkRequestBody(route *Route, types map[string]TypeExpr, linePrefix string) error {
- 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())
- }
- }
- return nil
- }
- func (p *Parser) checkTypes(apiItem *Api, linePrefix string, types map[string]TypeExpr) error {
- 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
- }
- }
- }
- 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
- }
- // SyntaxError accepts errors and panic it
- 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)
- }
- // WithParserDebug returns a debug ParserOption
- func WithParserDebug() ParserOption {
- return func(p *Parser) {
- p.debug = true
- }
- }
- // WithParserPrefix returns a prefix ParserOption
- func WithParserPrefix(prefix string) ParserOption {
- return func(p *Parser) {
- p.linePrefix = prefix
- }
- }
|