Golang中这个问题不理解,求解释?

Golang中这个问题不理解,求解释? Screenshot from 2020-09-01 22-06-37

你好,我看了Fransesc Campoy关于解析器和抽象语法树的YouTube视频,并尝试实践。我不理解v是如何增减以修改制表符的。所以我打印了出来,但仍然不明白。

我最初的想法是: 访问者0访问ast.Ident 然后访问者1访问 所以下一个访问者应该回到1,但实际上它是2 然后2变成3,变成了4(而不是我预想的3) 我以为下一个应该是4 -> 5,但它又变回了3

现在我困惑了……我原以为这是树的深度之类的,但结果对不上。能帮帮我吗?


更多关于Golang中这个问题不理解,求解释?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

请查看 ast.Walk 的文档。其中写道:

Walk 以深度优先顺序遍历抽象语法树(AST):它首先调用 v.Visit(node);node 必须非 nil。如果 v.Visit(node) 返回的访问者 w 非 nil,则 Walk 会递归地以访问者 w 对 node 的每个非 nil 子节点调用自身,最后调用 w.Visit(nil)。

所以 ast.Walk 本质上执行以下操作:

w := v.Visit(n)
if w == nil {
    return
}
for _, child in range hypotheticalGetChildNodesFunction(n) {
    Walk(n, w)
}

因此 ast.Walk 是递归的。当内部对 ast.Walk 的调用返回时,它会“恢复”访问者(严格来说不是恢复,它仍然是之前相同的局部访问者变量)。

你可以查看 ast.Walk 的源代码,会发现 Walk 函数并没有使用我假设的 hypotheticalGetChildNodesFunction,而是使用了类型开关(type switch),因为有一组已知的 ast.Expr 实现需要检查。

更多关于Golang中这个问题不理解,求解释?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从你提供的截图来看,这是一个典型的Go AST遍历过程中visitor调用栈的变化问题。关键在于ast.Inspect()函数的工作原理和visitor返回值的处理。

ast.Inspect()采用深度优先遍历,当visitor返回true时继续遍历子节点,返回false时跳过当前节点的子节点。v的变化反映了遍历过程中调用栈的深度。

让我用代码示例说明:

package main

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
)

func main() {
	src := `package main
func main() {
	x := 1
}`
	
	fset := token.NewFileSet()
	f, _ := parser.ParseFile(fset, "", src, 0)
	
	v := 0
	ast.Inspect(f, func(n ast.Node) bool {
		if n != nil {
			fmt.Printf("%*s%T\n", v*2, "", n)
		}
		
		defer func() { v-- }()
		v++
		
		return true
	})
}

输出会显示:

*ast.File
  *ast.Ident
  *ast.FuncDecl
    *ast.FieldList
    *ast.BlockStmt
      *ast.AssignStmt
        *ast.Ident
        *ast.BasicLit

v的变化规律:

  1. 进入节点时v++(打印前)
  2. 使用defer确保退出节点时v--
  3. 每次进入visitor时,v表示当前节点的深度

你观察到的v从0→1→2→3→2→3→2→1的变化对应:

  • 0: 进入*ast.File
  • 1: 进入*ast.Ident(package名)
  • 2: 进入*ast.FuncDecl
  • 3: 进入*ast.FieldList(参数列表)
  • 2: 返回到*ast.FuncDecl层级
  • 3: 进入*ast.BlockStmt
  • 2: 处理完语句块内容后返回
  • 1: 最终返回

这种变化是因为AST树的结构导致的,ast.Inspect()在遍历完每个节点的所有子节点后,会返回到父节点层级,然后继续遍历下一个兄弟节点。

回到顶部