the*_*ses 4 abstract-syntax-tree go
仅通过源代码(无需运行时检查)从文件位置获取周围函数名称的最佳方法是什么?
例如说我有一些代码:
func MyFunc() {
doSomething() // package/file.go:215:15
}
Run Code Online (Sandbox Code Playgroud)
而我有doSomething的位置,at package/file.go:215:15,有没有办法轻松获取MyFunc?
包含“最好”一词的问题总是很难回答,因为这很大程度上取决于您的要求,而您尚未指出这些要求。
我想了两种方法来解决这个问题,都有自己的优点和缺点:
快速而肮脏的方法是循环遍历每一行代码,并在我们到达感兴趣的行之前查找最近出现的函数减速。哪个应该是包含我们的行的函数。
package main
import (
"bufio"
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
func main() {
if len(os.Args) < 2 {
usage()
}
loc := strings.Split(os.Args[1], ":")
if len(loc) != 2 {
usage()
}
filePath := loc[0]
lineStr := loc[1]
targetLine, err := strconv.Atoi(lineStr)
if err != nil {
fmt.Println(err.Error())
usage()
}
f, err := os.Open(filePath)
if err != nil {
fmt.Println(err.Error())
usage()
}
defer f.Close()
lineScanner := bufio.NewScanner(f)
line := 0
var lastFunc string
for lineScanner.Scan() {
m := funcName.FindStringSubmatch(lineScanner.Text())
if len(m) > 0 {
lastFunc = m[1]
}
if line == targetLine {
fmt.Println(lastFunc)
return
}
line++
}
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s {file:line}\n", os.Args[0])
os.Exit(1)
}
// Look for a func followed by anything and ending at a `(` or ` `(space).
var funcName = regexp.MustCompile(`func ([^ (]+)`)
Run Code Online (Sandbox Code Playgroud)
优点:
缺点:
(和 是唯一结束 func 声明的字符,并且我们假设代码已格式化,以便 1 行上永远不会有 2 个 func 声明。更“规范正确”的方法是使用 go 编译器也使用的“官方” go 解析器来解析 go 代码。这会产生一个 AST(抽象语法树),我们可以使用 DFS(深度优先搜索)算法遍历它,找到包含我们位置的最具体的 AST 节点,同时还可以找到最新的函数减速。
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"regexp"
"strconv"
"strings"
)
func main() {
if len(os.Args) < 2 {
usage()
}
var pos token.Position
loc := strings.Split(os.Args[1], ":")
if len(loc) >= 2 {
pos.Filename = loc[0]
line, err := strconv.Atoi(loc[1])
if err != nil {
fmt.Println(err.Error())
usage()
}
pos.Line = line
} else {
usage()
}
if len(loc) >= 3 {
col, err := strconv.Atoi(loc[2])
if err != nil {
fmt.Println(err.Error())
usage()
}
pos.Column = col
}
file, err := os.Open(pos.Filename)
if err != nil {
fmt.Println(err.Error())
usage()
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", file, 0)
if err != nil {
fmt.Println(err.Error())
usage()
}
var lastFunc *ast.FuncDecl
ast.Inspect(f, func(n ast.Node) bool {
if n == nil {
return false
}
// Store the most specific function declaration
if funcDecl, ok := n.(*ast.FuncDecl); ok {
lastFunc = funcDecl
}
start := fset.Position(n.Pos())
end := fset.Position(n.End())
// Don't traverse nodes which don't contain the target line
if start.Line > pos.Line || end.Line < pos.Line {
return false
}
// If node starts and stops on the same line
if start.Line == pos.Line && end.Line == pos.Line {
// Don't traverse nodes which don't contain the target column
if start.Column > pos.Column || end.Column < pos.Column {
return false
}
}
// Note, the very last node to be traversed is our target node
return true
})
if lastFunc != nil {
fmt.Println(lastFunc.Name.String())
}
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s {file:line:column}\n", os.Args[0])
os.Exit(1)
}
Run Code Online (Sandbox Code Playgroud)
优点:
缺点: