feat(goctl): Add api parser (#2585)
This commit is contained in:
223
tools/goctl/pkg/parser/api/ast/ast.go
Normal file
223
tools/goctl/pkg/parser/api/ast/ast.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/zeromicro/go-zero/tools/goctl/pkg/parser/api/token"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
// Node represents a node in the AST.
|
||||
type Node interface {
|
||||
// Pos returns the position of the first character belonging to the node.
|
||||
Pos() token.Position
|
||||
// End returns the position of the first character immediately after the node.
|
||||
End() token.Position
|
||||
// Format returns the node's text after format.
|
||||
Format(...string) string
|
||||
// HasHeadCommentGroup returns true if the node has head comment group.
|
||||
HasHeadCommentGroup() bool
|
||||
// HasLeadingCommentGroup returns true if the node has leading comment group.
|
||||
HasLeadingCommentGroup() bool
|
||||
// CommentGroup returns the node's head comment group and leading comment group.
|
||||
CommentGroup() (head, leading CommentGroup)
|
||||
}
|
||||
|
||||
// Stmt represents a statement in the AST.
|
||||
type Stmt interface {
|
||||
Node
|
||||
stmtNode()
|
||||
}
|
||||
|
||||
// Expr represents an expression in the AST.
|
||||
type Expr interface {
|
||||
Node
|
||||
exprNode()
|
||||
}
|
||||
|
||||
// AST represents a parsed API file.
|
||||
type AST struct {
|
||||
Filename string
|
||||
Stmts []Stmt
|
||||
readPosition int
|
||||
}
|
||||
|
||||
// TokenNode represents a token node in the AST.
|
||||
type TokenNode struct {
|
||||
// HeadCommentGroup are the comments in prev lines.
|
||||
HeadCommentGroup CommentGroup
|
||||
// Token is the token of the node.
|
||||
Token token.Token
|
||||
// LeadingCommentGroup are the tail comments in same line.
|
||||
LeadingCommentGroup CommentGroup
|
||||
|
||||
// headFlag and leadingFlag is a comment flag only used in transfer another Node to TokenNode,
|
||||
// headFlag's value is true do not represent HeadCommentGroup is not empty,
|
||||
// leadingFlag's values is true do not represent LeadingCommentGroup is not empty.
|
||||
headFlag, leadingFlag bool
|
||||
}
|
||||
|
||||
// NewTokenNode creates and returns a new TokenNode.
|
||||
func NewTokenNode(tok token.Token) *TokenNode {
|
||||
return &TokenNode{Token: tok}
|
||||
}
|
||||
|
||||
// IsEmptyString returns true if the node is empty string.
|
||||
func (t *TokenNode) IsEmptyString() bool {
|
||||
return t.Equal("")
|
||||
}
|
||||
|
||||
// IsZeroString returns true if the node is zero string.
|
||||
func (t *TokenNode) IsZeroString() bool {
|
||||
return t.Equal(`""`) || t.Equal("``")
|
||||
}
|
||||
|
||||
// Equal returns true if the node's text is equal to the given text.
|
||||
func (t *TokenNode) Equal(s string) bool {
|
||||
return t.Token.Text == s
|
||||
}
|
||||
|
||||
// SetLeadingCommentGroup sets the node's leading comment group.
|
||||
func (t *TokenNode) SetLeadingCommentGroup(cg CommentGroup) {
|
||||
t.LeadingCommentGroup = cg
|
||||
}
|
||||
|
||||
func (t *TokenNode) HasLeadingCommentGroup() bool {
|
||||
return t.LeadingCommentGroup.Valid() || t.leadingFlag
|
||||
}
|
||||
|
||||
func (t *TokenNode) HasHeadCommentGroup() bool {
|
||||
return t.HeadCommentGroup.Valid() || t.headFlag
|
||||
}
|
||||
|
||||
func (t *TokenNode) CommentGroup() (head, leading CommentGroup) {
|
||||
return t.HeadCommentGroup, t.LeadingCommentGroup
|
||||
}
|
||||
|
||||
// PeekFirstLeadingComment returns the first leading comment of the node.
|
||||
func (t *TokenNode) PeekFirstLeadingComment() *CommentStmt {
|
||||
if len(t.LeadingCommentGroup) > 0 {
|
||||
return t.LeadingCommentGroup[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PeekFirstHeadComment returns the first head comment of the node.
|
||||
func (t *TokenNode) PeekFirstHeadComment() *CommentStmt {
|
||||
if len(t.HeadCommentGroup) > 0 {
|
||||
return t.HeadCommentGroup[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TokenNode) Format(prefix ...string) string {
|
||||
p := peekOne(prefix)
|
||||
var textList []string
|
||||
for _, v := range t.HeadCommentGroup {
|
||||
textList = append(textList, v.Format(p))
|
||||
}
|
||||
|
||||
var tokenText = p + t.Token.Text
|
||||
var validLeadingCommentGroup CommentGroup
|
||||
for _, e := range t.LeadingCommentGroup {
|
||||
if util.IsEmptyStringOrWhiteSpace(e.Comment.Text) {
|
||||
continue
|
||||
}
|
||||
validLeadingCommentGroup = append(validLeadingCommentGroup, e)
|
||||
}
|
||||
|
||||
if len(validLeadingCommentGroup) > 0 {
|
||||
tokenText = tokenText + WhiteSpace + t.LeadingCommentGroup.Join(WhiteSpace)
|
||||
}
|
||||
|
||||
textList = append(textList, tokenText)
|
||||
return strings.Join(textList, NewLine)
|
||||
}
|
||||
|
||||
func (t *TokenNode) Pos() token.Position {
|
||||
if len(t.HeadCommentGroup) > 0 {
|
||||
return t.PeekFirstHeadComment().Pos()
|
||||
}
|
||||
return t.Token.Position
|
||||
}
|
||||
|
||||
func (t *TokenNode) End() token.Position {
|
||||
if len(t.LeadingCommentGroup) > 0 {
|
||||
return t.LeadingCommentGroup[len(t.LeadingCommentGroup)-1].End()
|
||||
}
|
||||
return t.Token.Position
|
||||
}
|
||||
|
||||
// Format formats the AST.
|
||||
func (a *AST) Format(w io.Writer) {
|
||||
fw := NewWriter(w)
|
||||
defer fw.Flush()
|
||||
for idx, e := range a.Stmts {
|
||||
if e.Format() == NilIndent {
|
||||
continue
|
||||
}
|
||||
|
||||
fw.Write(withNode(e))
|
||||
fw.NewLine()
|
||||
switch e.(type) {
|
||||
case *SyntaxStmt:
|
||||
fw.NewLine()
|
||||
case *ImportGroupStmt:
|
||||
fw.NewLine()
|
||||
case *ImportLiteralStmt:
|
||||
if idx < len(a.Stmts)-1 {
|
||||
_, ok := a.Stmts[idx+1].(*ImportLiteralStmt)
|
||||
if !ok {
|
||||
fw.NewLine()
|
||||
}
|
||||
}
|
||||
case *InfoStmt:
|
||||
fw.NewLine()
|
||||
case *ServiceStmt:
|
||||
fw.NewLine()
|
||||
case *TypeGroupStmt:
|
||||
fw.NewLine()
|
||||
case *TypeLiteralStmt:
|
||||
fw.NewLine()
|
||||
case *CommentStmt:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FormatForUnitTest formats the AST for unit test.
|
||||
func (a *AST) FormatForUnitTest(w io.Writer) {
|
||||
fw := NewWriter(w)
|
||||
defer fw.Flush()
|
||||
for _, e := range a.Stmts {
|
||||
text := e.Format()
|
||||
if text == NilIndent {
|
||||
continue
|
||||
}
|
||||
|
||||
fw.WriteText(text)
|
||||
}
|
||||
}
|
||||
|
||||
// Print prints the AST.
|
||||
func (a *AST) Print() {
|
||||
_ = Print(a)
|
||||
}
|
||||
|
||||
// SyntaxError represents a syntax error.
|
||||
func SyntaxError(pos token.Position, format string, v ...interface{}) error {
|
||||
return fmt.Errorf("syntax error: %s %s", pos.String(), fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// DuplicateStmtError represents a duplicate statement error.
|
||||
func DuplicateStmtError(pos token.Position, msg string) error {
|
||||
return fmt.Errorf("duplicate declaration: %s %s", pos.String(), msg)
|
||||
}
|
||||
|
||||
func peekOne(list []string) string {
|
||||
if len(list) == 0 {
|
||||
return ""
|
||||
}
|
||||
return list[0]
|
||||
}
|
||||
Reference in New Issue
Block a user