Основное оружие Go — обход AST

Go

Первоисточник: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/tokenPackage для создания нового 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, если вам интересно, вы можете взглянуть