You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-zero/tools/goctl/pkg/parser/api/ast/ast.go

237 lines
5.8 KiB
Go

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
}
// RawText returns the node's raw text.
func (t *TokenNode) RawText() string {
text := t.Token.Text
if strings.HasPrefix(text, "`") {
text = strings.TrimPrefix(text, "`")
text = strings.TrimSuffix(text, "`")
} else if strings.HasPrefix(text, `"`) {
text = strings.TrimPrefix(text, `"`)
text = strings.TrimSuffix(text, `"`)
}
return text
}
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]
}