Первоисточник:DP Jeep.com/go - первый тест…
задний план
В последнее время некоторые средства автоматизации необходимо делать на основе AST, поэтому также необходимо провести некоторые исследования этого волшебного оружия. Эта статья также подготовлена для простого объяснения следующих двух частей:
- Разобрать программу Go через AST
- Затем проанализируйте этот AST с помощью стандартной библиотеки Go.
AST
чтоAST, на самом деле является аббревиатурой абстрактного синтаксического дерева. Он представляет синтаксическую структуру языка программирования в виде дерева, и каждый узел в дереве представляет собой структуру в исходном коде. Грамматика называется «абстрактной», потому что грамматика здесь не представляет все детали, которые встречаются в реальной грамматике.
основное блюдо
напоминание о закуске
Следующее содержание немного длинное, почему бы вам не купить семечки дыни и не понаблюдать за ними, пока вы их разбиваете?
процесс компиляции
Чтобы объяснить соответствующие части AST, давайте кратко поговорим об известном нам процессе компиляции:
- лексический анализ
- Разбор
- Семантический анализ и генерация промежуточного кода
- оптимизация компилятора
- генерация объектного кода И то, что мы собираемся использовать сейчас, — это набор очень удобных инструментов лексического и синтаксического анализа, подготовленных Google для нас, с помощью которых мы можем построить автомобиль.
пример кода
Экземпляры были предоставлены в официальной документации Golang, поэтому мы не будем их здесь включать.Исходный код документацииОпубликовано, выпущены только некоторые варианты использования
// This example shows what an AST looks like when printed for debugging.
func ExamplePrint() {
// src is the input for which we want to print the AST.
src := `
package main
func main() {
println("Hello, World!")
}
`
// Create the AST by parsing src.
fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
// Print the AST.
ast.Print(fset, f)
// Output:
// 0 *ast.File {
// 1 . Package: 2:1
// 2 . Name: *ast.Ident {
// 3 . . NamePos: 2:9
// 4 . . Name: "main"
// 5 . }
// 6 . Decls: []ast.Decl (len = 1) {
// 7 . . 0: *ast.FuncDecl {
// 8 . . . Name: *ast.Ident {
// 9 . . . . NamePos: 3:6
// 10 . . . . Name: "main"
// 11 . . . . Obj: *ast.Object {
// 12 . . . . . Kind: func
// 13 . . . . . Name: "main"
// 14 . . . . . Decl: *(obj @ 7)
// 15 . . . . }
// 16 . . . }
// 17 . . . Type: *ast.FuncType {
// 18 . . . . Func: 3:1
// 19 . . . . Params: *ast.FieldList {
// 20 . . . . . Opening: 3:10
// 21 . . . . . Closing: 3:11
// 22 . . . . }
// 23 . . . }
// 24 . . . Body: *ast.BlockStmt {
// 25 . . . . Lbrace: 3:13
// 26 . . . . List: []ast.Stmt (len = 1) {
// 27 . . . . . 0: *ast.ExprStmt {
// 28 . . . . . . X: *ast.CallExpr {
// 29 . . . . . . . Fun: *ast.Ident {
// 30 . . . . . . . . NamePos: 4:2
// 31 . . . . . . . . Name: "println"
// 32 . . . . . . . }
// 33 . . . . . . . Lparen: 4:9
// 34 . . . . . . . Args: []ast.Expr (len = 1) {
// 35 . . . . . . . . 0: *ast.BasicLit {
// 36 . . . . . . . . . ValuePos: 4:10
// 37 . . . . . . . . . Kind: STRING
// 38 . . . . . . . . . Value: "\"Hello, World!\""
// 39 . . . . . . . . }
// 40 . . . . . . . }
// 41 . . . . . . . Ellipsis: -
// 42 . . . . . . . Rparen: 4:25
// 43 . . . . . . }
// 44 . . . . . }
// 45 . . . . }
// 46 . . . . Rbrace: 5:1
// 47 . . . }
// 48 . . }
// 49 . }
// 50 . Scope: *ast.Scope {
// 51 . . Objects: map[string]*ast.Object (len = 1) {
// 52 . . . "main": *(obj @ 11)
// 53 . . }
// 54 . }
// 55 . Unresolved: []*ast.Ident (len = 1) {
// 56 . . 0: *(obj @ 29)
// 57 . }
// 58 }
}
Чувствуете ли вы легкое головокружение, когда видите отпечаток выше? Хаха я тоже. Я не ожидал, что простой hello world может напечатать так много всего, в нем действительно скрыто много интересных элементов, таких как функции, переменные, комментарии, импорты и т. д. Так как же мы можем извлечь из него нужные нам данные? Для этого нам нужно использовать то, что нам предоставляет Golang.go/parser
Сумка:
// Create the AST by parsing src.
fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
Ссылки на первую строкуgo/token
Package для создания нового FileSet исходных файлов для разбора.
Затем мы используемparser.ParseFile
возвращаетast.File
тип структуры (оригинальный документ), а затем снова посмотрите на распечатку журнала выше. Возможно, вам понравилось значение каждого элемента поля. Структура определяется следующим образом:
type File struct {
Doc *CommentGroup // associated documentation; or nil
Package token.Pos // position of "package" keyword
Name *Ident // package name
Decls []Decl // top-level declarations; or nil
Scope *Scope // package scope (this file only)
Imports []*ImportSpec // imports in this file
Unresolved []*Ident // unresolved identifiers in this file
Comments []*CommentGroup // list of all comments in the source file
}
Что ж, теперь мы собираемся использовать эту структуру для создания небольшого примера кода, давайте разберем следующий файлast_traversal.go
:
package ast_demo
import "fmt"
type Example1 struct {
// Foo Comments
Foo string `json:"foo"`
}
type Example2 struct {
// Aoo Comments
Aoo int `json:"aoo"`
}
// print Hello World
func PrintHello(){
fmt.Println("Hello World")
}
Мы уже можем использовать вышеупомянутыйast.File
структура для разбора этого файла, например, использованиеf.Imports
Список пакетов, на которые ссылаются:
for _, i := range f.Imports {
t.Logf("import: %s", i.Path.Value)
}
Точно так же мы можем отфильтровать комментарии, функции и т. д., например:
for _, i := range f.Comments {
t.Logf("comment: %s", i.Text())
}
for _, i := range f.Decls {
fn, ok := i.(*ast.FuncDecl)
if !ok {
continue
}
t.Logf("function: %s", fn.Name.Name)
}
Выше способ получения комментария аналогичен импорту, его можно использовать напрямую, а для функций он используется*ast.FucDecl
Кстати, в это время перейдите на верхний уровень этой статьи, проверьте печать дерева AST, и вы найдетеDecls: []ast.Decl
Он хранится в виде массива, и в нем хранится несколько типов узлов, здесь с помощью принудительного преобразования типов определяется, существует ли определенный тип, и если он существует, то выводится в соответствии со структурой в типе. Вышеупомянутый метод смог удовлетворить наши основные потребности и может быть специально проанализирован для определенного типа.
Тем не менее, есть еще одно но, ха-ха, не сложно ли разобрать один за другим описанным выше методом? Все в порядке, папа Гугл прошелgo/ast
Пакет предоставляет нам еще один удобный и быстрый способ:
// Inspect traverses an AST in depth-first order: It starts by calling
// f(node); node must not be nil. If f returns true, Inspect invokes f
// recursively for each of the non-nil children of node, followed by a
// call of f(nil).
//
func Inspect(node Node, f func(Node) bool) {
Walk(inspector(f), node)
}
Общее использование этого метода таково: весь переданный AST анализируется методом поиска в глубину, который начинается с вызова f(node); узел не может быть нулевым. Если f возвращает true, Inspect рекурсивно вызывает f для каждого ненулевого дочернего элемента узла, а затем вызывает f(nil). Соответствующие варианты использования следующие:
ast.Inspect(f, func(n ast.Node) bool {
// Find Return Statements
ret, ok := n.(*ast.ReturnStmt)
if ok {
t.Logf("return statement found on line %d:\n\t", fset.Position(ret.Pos()).Line)
printer.Fprint(os.Stdout, fset, ret)
return true
}
// Find Functions
fn, ok := n.(*ast.FuncDecl)
if ok {
var exported string
if fn.Name.IsExported() {
exported = "exported "
}
t.Logf("%sfunction declaration found on line %d: %s", exported, fset.Position(fn.Pos()).Line, fn.Name.Name)
return true
}
return true
})
постскриптум
К этому моменту вы, возможно, закончили есть семена в руке.У AST много применений.То, что мы упомянули выше, является лишь небольшой частью AST.Многие низкоуровневые инструменты анализа основаны на нем для грамматического анализа.Инструменты находятся в руках, тогда дело за мастерами, чтобы сделать какое произведение искусства. В дальнейшем некоторые гаджеты на базе Go AST будут обновляться один за другим, надеюсь смогу реализовать как можно быстрее, ха-ха 😆.
Ниже приведены тестовые случаи, использованные в приведенном выше, и исходный код для разбора полей структуры с использованием AST, Я отправил его на Github, если вам интересно, вы можете взглянуть