Skip to content

Latest commit

 

History

History
258 lines (220 loc) · 12.1 KB

README-ZH.org

File metadata and controls

258 lines (220 loc) · 12.1 KB

goastch (GO AST matCH)

NOTE: 开发阶段

  1. 添加新接口
  2. 完善接口功能

介绍

clang 实现了 ast matcher, 其中将匹配器分为 节点匹配器,遍历匹配器,以及属性匹配器,是 goastch 实现的主要依据。 此方法可以理解为对语法树进行不同方式的遍历,同时进行节点筛选。

特性

goastch, 可用来匹配任意 go 代码块(语法树)。 匹配器主要有四类:

  • 节点匹配器: 匹配 go/ast 中定义的语法节点
  • 遍历匹配器: 以不同的方式遍历语法节点,用来查找某些节点
  • 属性匹配器: 匹配节点的属性
  • 逻辑匹配器: 将以上匹配器逻辑组合,实现更灵活的匹配

除此之外还添加一些特殊的匹配器

  • 错误匹配器: 用来处理匹配器创建过程中的错误

关于匹配器的实现请看 此外 goastch 提供了一个 DSL 用以方便的嵌入 goastch 到某些应用(比如 govet)

使用

ga 解释器

ga 用来查找包/源文件中的代码

ga 解释器演示 ./docs/imgs/ga.gif

匹配器接口

所有匹配器接口都在 "github.com/helloyi/goastch/goastcher" 包中实现。 此包在保证匹配器可用的前提下,尽量保持最精简/少种类的可导出对象。 友好的使用方式是使用点导入,这样链式调用会更加简洁。

ger := File(Has(ImportSpec(HasName(equals("."))).Bind("import")))

matched, err := goastch.Match(ast, info, ger)
bindings, err := goastch.Find(ast, info, ger)

如上,匹配器从左到右依次匹配,对于任意匹配器其左边的匹配器称为上游匹配器, 右边的匹配器称为下游匹配器。匹配器以遍历匹配器开始,遍历匹配器是匹配链的驱动者, 如果起始匹配器不是遍历匹配器,在执行匹配的时候会自动添加 HasDescendant 匹配器。 遍历匹配器,遍历上游匹配器通过的节点,并依次执行下游匹配器。如果给定语法树中包含可以走完匹配链的节点则匹配成功。

匹配成功的节点默认并不会保存,只有使用 Bind 方法可将通过此匹配器的节点保存下来。 并使用 Find 函数获取匹配结果。

下面是一个匹配空 Slice 短声明的完整例子

package main

import (
	"fmt"
	"go/parser"
	"go/printer"
	"go/token"
	"log"
	"os"

	"github.com/helloyi/goastch"
	. "github.com/helloyi/goastch/goastcher"
)

func main() {
	g := ShortVarDecl(Has(CompositeLit(IsSize(0)))).Bind("bindID")

	src := `package foo
	func bar() {
    a := []int{}
    var b []int
    c := []string{}
	}`

	fset := token.NewFileSet()
	file, _ := parser.ParseFile(fset, "example", src, 0)
	bindings, err := goastch.Find(file, nil, g)
	if err != nil {
		log.Fatalln(err)
	}
	for key, list := range bindings {
		for _, node := range list {
			fmt.Printf("%s: ", key)
			_ = printer.Fprint(os.Stdout, fset, node)
			fmt.Println()
		}
	}
}

遍历匹配器

遍历匹配器(Traversal Goastcher), 以特定的方式遍历给定节点。 命名约定:

  • has*: 给定节点有某个节点; 对于节点数组,则表示长度为 1 的数组,并传递该节点到下游
  • for*: 遍历给定节点下的对应数组节点,并以此传递给下游

has 匹配器

名字描述
HasDescendant查询后继节点
Has查询孩子节点
HasName查找节点的 Name
HasValue查找节点的 Value
HasRecvName查找函数的接收器名
HasRhs查找表达式右边
HasResults查找节点 Results
HasType查找节点 Type

for 匹配器

名字说明
ForDecls遍历节点的 Decls
ForSpecs遍历节点的 Specs
ForNames遍历节点的 Names
ForFields遍历节点的 Fields

节点匹配器

节点匹配器(Node Goastcher), 判断给定节点是否是某类型节点。命名为 go/ast 下定义的节点名, 除此之外是在 go/ast 定义的基础上,做一些属性限制而产生的节点匹配器。

基本节点匹配器

匹配器名说明
ArrayType数组类型
AssignStmt赋值语句
BadDecl错误声明
BadExpr错误表达式
BadStmt错误语句
BasicLit书面值, 整数, 字符串等基本类型的字面值
BinaryExpr二元表达式
BlockStmt’{’ ‘}’ 括起的语句列表
BranchStmt分支/跳转语句, goto, break 等改变程序流程的表达式
CallExpr函数调用表达式
CaseClauseswitch 的 case 语句块
ChanTypeChannal 类型
CommClauseselect 的 case 语句块
Comment表示 ‘//’ 或者 ’* *’ 形式的注释
CommentGroup连续的注释,不包括空行
CompositeLit’{’ ‘}’ 括起来的表达式列表
DeclStmt声明语句
DeferStmtdefer 语句
Ellipsis‘…’ 参数,或者 ‘…’ 数组长度
EmptyStmt空语句,显示或隐式的 ‘;’
ExprStmt语句列表中独立的表达式
Field结构体,接口的方法列表,函数参数声明,函数返回值声明中的一个域
FieldList域列表
File文件
ForStmtfor 语句
FuncDecl函数声明
FuncLit匿名函数
FuncType函数类型
GenDeclimport,const,type,var 声明
GoStmtgo 语句
Ident标识符(变量名)
IfStmtif 语句
ImportSpec表示一条包导入语句
IncDecStmt自增自减语句
IndexExpr数组,切片索引表达式 a[1]
InterfaceType接口类型
KeyValueExpr键值对 key: value
LabeledStmt有标签的语句块
MapTypemap 类型
Pkg一个包,包含多个文件
ParenExpr’(’ ‘)’ 括起来的表达式
RangeStmtrange 语句
ReturnStmtreturn 语句
SelectStmtselect 语句
SelectorExpr’.’ 选择器表达式
SendStmt‘<-’ 语句
SliceExpr切片表达式
StarExpr’*’ 表达式
StructType结构体类型
SwitchStmtswitch 语句
TypeAssertExpr类型断言表达式
TypeSpec类型声明 type a = b
TypeSwitchStmt类型 switch 语句
UnaryExpr一元表达式

基于基本节点匹配器演化出的节点匹配器

匹配器名说明
ShortVarDecl短变量声明,即使用 ‘:=’ 的赋值语句
sliceType切片类型
intBasicLit整数值
floatBasicLit浮点值
imagBasicLit实数值
charBasicLit字符值
stringBasicLit字符串值

属性匹配器

属性匹配器,匹配节点的属性。

属性匹配器描述
AsCode将节点作为 go 源码匹配
MatchCode使用正则表达式匹配节点所表示的代码
IsSize复合节点大小
HasOperator节点是否有给定操作符
IsType节点是否是给定类型
HasPrefix标识符是否有给定前缀
HasSuffix标识符是否有给定后缀
Contains标识符是否包含给定子串
MatchString标识符是否匹配给定正则表达式
Equals标识符/字面值是否和给定值相同

逻辑匹配器

名字参数描述
AllOf一个或多个匹配器给定的所有 goastcher 都满足则匹配成功
AnyOf一个或多个匹配器只要一个 goastcher 满足则匹配成功
Unless一个匹配器非操作

DSL

此 DSL 目标是提供一个全功能的代码匹配语言:

  • 方便嵌入到其他应用
  • 方便的查找某些代码块
  • 对查找结果做特定处理 (比如以简单的方式写 go generate)

需要实现的功能:

  • [X] goastcher 匹配表达式
  • [ ] 在指定 go 源码(包路径/文件路径)上执行匹配表达式
  • [ ] 匹配结果处理

目前只实现了匹配表达式。

File(Has(ImportSpec(HasName(equals("."))).Bind("")))

对应如上边的匹配器,转换为匹配表达式为

File has @importSpec hasName equals "."

此 DSL 实现了所有 goastcher api 的匹配器,并且不区分大小写。特殊的, @ 符号表示 Bind 此匹配器通过的节点。

文法定义

匹配表达式 goastcher

ger          = [ '@' ] nodeGer | travelGer | narrowGer | logicGer
nodeGer      = nodeIdent ger
travelGer    = travelIdent ger
narrowGer    = narrowIdent ger
logicGer     = logicIdent compositeGer
compositeGer = ger { 'and' ger } [ ',' ]