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.
224 lines
5.5 KiB
Go
224 lines
5.5 KiB
Go
2 years ago
|
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]
|
||
|
}
|