使用go/parser跨包的用法

huangapple go评论76阅读模式
英文:

Usage of go/parser across packages

问题

我使用go/parser来解析一个Go语言文件并检查其AST。我有一个特定的问题,想要使用go/parser,但是遇到了一个障碍。

假设在GOPATH/src目录下存在以下文件:

$GOPATH/src/
    example.go
    example_package/
        example_package.go

以下是上述文件的内容:

example.go

package main

import (
    "example_package"
)

type MyObject struct {
    base *example_package.BaseObject
}

func DoMyThing(arg *example_package.FirstArg) {
    arg.Write(10)
}

func DoMyAnotherThing() {
}

func main() {
    example_package.GetItStarted(&MyObject{})
}

example_package.go

package example_package

func GetItStarted(obj interface{}) {
}

type FirstArg interface {
    Read() int
    Write(x int)
}

type BaseObject struct {
}

func (p *BaseObject) DoSomething(arg *FirstArg, a int) {
    arg.Write(arg.Read() + a)
}

我的意图是编写一个名为gen_structure的Go程序,用法如下:

$ gen_structure example.go

输出应为:

> MyObject
- DoMyThing(arg)
- base
    - DoSomething(arg, a)

gen_structure做了什么?

它解析了example.go,并且:

  1. 从main()函数内的example_package.GetItStarted(&MyObject{})这一行中提取了"MyObject"。
  2. 查找MyObject上至少有一个参数的方法,其中第一个参数的类型为*package_example.FirstArg。它找到了DoMyThing(并忽略了DoMyAnotherThing)。
  3. 识别了成员base并查看其内部(通过打开example_package)。
  4. 使用与上述相同的过程查找方法,并找到了DoSomething
  5. 使用收集到的信息打印所需的输出。

我了解到可以使用go/parser中的功能来解析单个文件或同一目录下的一组文件。然而,我无法弄清楚如何解析跨包的符号(在这种情况下是example_package)。

我该如何做到这一点?

英文:

I have used go/parser to parse a golang file and examine it's AST. I have a specific problem for which I want to use go/parser but I hit a roadblock.

Consider that the following files are present in GOPATH/src

$GOPATH/src/
    example.go
    example_package/
        example_package.go

The following are the contents of the files above

example.go

package main

import (
    "example_package"
)

type MyObject struct {
    base *example_package.BaseObject
}

func DoMyThing(arg *example_package.FirstArg) {
    arg.Write(10)
}

func DoMyAnotherThing() {
}

func main() {
    example_package.GetItStarted(&MyObject{})
}

example_package.go

package example_package

func GetItStarted(obj interface{}) {
}

type FirstArg interface {
    Read() int
    Write(x int)
}

type BaseObject struct {
}

func (p *BaseObject) DoSomething(arg *FirstArg, a int) {
    arg.Write(arg.Read() + a)
}

My intention is to write a go program called gen_structure that is used like this

$ gen_structure example.go

The output would be

> MyObject
- DoMyThing(arg)
- base
    - DoSomething(arg, a)

What did gen_structure do?

It parses example.go and

  1. Extracts "MyObject" from the line example_package.GetItStarted(&MyObject{}) from inside the main() function.
  2. Looks for methods on MyObject that have atleast one argument with the first one being of type *package_example.FirstArg. It finds DoMyThing (and ignored DoMyAnotherThing).
  3. Identifies the member base and peeks inside (by opening the example_package).
  4. Applies the same process to find methods as above and finds DoSomething
  5. Using the collected information, it prints the required output.

I understand I can parse a single file or a bunch of files in the same directory using the functionality within go/parser. However, I am unable to figure out how to resolve symbols across packages (In this case, example_package).

How do I do this?

答案1

得分: 5

调用ast.NewPackage来解析包名。您需要提供一个返回给定导入路径的*ast.Objectimporter。如果您只想将名称解析为路径,导入器可以简单地返回一个*ast.Object,其中Kind设置为ast.PkgName设置为包的名称。导入器中的大部分工作可以使用go/build包完成。如果要解析目标包的AST,您需要解析该包并返回包的ast.Object。为了防止多次加载相同的包,可以使用导入器的映射参数作为先前加载的包的缓存。

以下是一些未经测试的代码,用于从*ast.SelectorExpr se中查找解析后的包路径:

if x, _ := se.X.(*ast.Ident); x != nil {
    if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
        if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
            if path, err := strconv.Unquote(spec.Path.Value); err == nil {
                // path 是选择表达式 se 的解析后路径。
            }
        }
    }
}

go/types包也可以用于获取此信息和更多信息。我建议使用go/types而不是直接使用go/ast。

英文:

Call ast.NewPackage to resolve a package names. You will need to supply an importer that returns an *ast.Object for the given import path. If all you want to do is resolve the name to a path, the importer can simply return an *ast.Object with the Kind set to ast.Pkg and the Name set to name of the package. Most of the heavy lifting in the importer can be done with the go/build package. If want to resolve do the AST for the target package, you will need to parse the package and return the ast.Object for the package. To prevent loading the same package multiple times, use the map argument to the importer as a cache of previously loaded packages.

Here's some untested code for finding the resolved package path from the *ast.SelectorExpr se:

	if x, _ := se.X.(*ast.Ident); x != nil {
		if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
			if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
				if path, err := strconv.Unquote(spec.Path.Value); err == nil {
                    // path is resolved path for selector expression se.
                }
            }
         }
     }

The go/types package can also be used to get this information and more. I recommend using go/types instead of using go/ast directly.

huangapple
  • 本文由 发表于 2015年9月12日 05:24:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/32532335.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定