以Golang为例详解AST抽象语法树的原理与实现

 更新时间:2024年01月17日 08:17:56   作者:灯火消逝的码头  
AST 使用树状结构来表达编程语言的结构,树中的每一个节点都表示源码中的一个结构,本文将以GO语言为例,为大家介绍一下AST抽象语法树的原理与实现,希望对大家有所帮助

前言

各位同行有没有想过一件事,一个程序文件,比如 hello.go 是如何被编译器理解的,平常在编写程序时,IDE 又是如何提供代码提示的。在这奥妙无穷的背后, AST(Abstract Syntax Tree)抽象语法树功不可没,他站在每一行程序的身后,默默无闻的工作,为繁荣的互联网世界立下了汗马功劳。

AST 抽象语法树

AST 使用树状结构来表达编程语言的结构,树中的每一个节点都表示源码中的一个结构。听到这或许你的心里会咯噔一下,其实说通俗一点,在源代码解析后会得到一串数据,这个数据自然的呈现树状结构,它被称之为 CST(Concrete Syntax Tree) 具体语法树,在 CST 的基础上保留核心结构。忽略一些不重要的结构,比如标点符号,空白符,括号等,就得到了 AST。

如何生成 AST 

生成 AST 大概需要两个步骤,词法分析lexical analysis和语法分析syntactic analysis 。

词法分析 lexical analysis

lexical analysis 简称 lexer ,它表示字符串序列,也就是我们的源代码转化为 token 的过程,进行词法分析的工具叫做词法分析器(lexical analyzer,简称lexer),也叫扫描器(scanner)。Go 语言的 go/scanner 包提供词法分析。 

func ScannerDemo() {
	// 源代码
	src := []byte(`
func demo() {
	fmt.Println("When you are old and gray and full of sleep")
}
`)

	// 初始化标记
	var s scanner.Scanner
	fset := token.NewFileSet()
	file := fset.AddFile("", fset.Base(), len(src))
	s.Init(file, src, nil, scanner.ScanComments)

	// Scan 进行扫码并打印出结果
	for {
		pos, tok, lit := s.Scan()
		if tok == token.EOF {
			break
		}
		fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit)
	}
}

打印的结果我们接着往下看。

标记 token

标记(token)  是词法分析后留下的产物,是构成源代码的最小单位,但是这些 token 之间没有任何逻辑关系。以上述代码为例:

func demo() {
	fmt.Println("When you are old and gray and full of sleep")
}

经过词法分析后,会得到:

tokenliteral(字面量,以string表示)
func"func"
IDENT"demo"
(""
)""
{""
IDENT"fmt"
.""
IDENT"Println"
(""
STRING"\"When you are old and gray and full of sleep\""
)""
;"\n"
}""
;"\n"

在 Go 语言中,如果 token 类型就是一个字面量,例如整型,字符串类型等,那么它的值就是相对应的值,比如上表的 STRING;如果 token 是 Go 的关键词,那么它的值就是关键词,比如上表的 fun;对于分号,它的值则是换行符;其他 token 类要么是不合法的,如果是合法的,则值为空字符串,比如上表的 {

语法分析 syntactic analysis

不具备逻辑关系的 token 经过语法分析(syntactic analysis,也叫 parsing)就可以得到具有逻辑关系的 CST 具体语法树,然后对 CST 进行分析提炼即可得到 AST 抽象语法树。完成语法分析的工具叫做语法分析器(parser)。Go 语言的 go/parser 提供语法分析。

func ParserDemo() {
	src := `
package main
`
	fset := token.NewFileSet()
	// 如果 src 为 nil,则使用第二个参数,它可以是一个 .go 文件地址
	f, err := parser.ParseFile(fset, "", src, 0)
	if err != nil {
		panic(err)
	}

	ast.Print(fset, f)
}

打印出来的 AST:

 0  *ast.File {
 1  .  Package: 2:1
 2  .  Name: *ast.Ident {
 3  .  .  NamePos: 2:9
 4  .  .  Name: "main"
 5  .  }
 6  .  FileStart: 1:1
 7  .  FileEnd: 2:14
 8  .  Scope: *ast.Scope {
 9  .  .  Objects: map[string]*ast.Object (len = 0) {}
10  .  }
11  }

它包含了源代码的结构信息,看起来像一个 JSON。

总结

源代码经过词法分析后得到 token(标记),token 经过语法分析得到 CST 具体语法树,在 CST 上创建 AST 抽象语法树。 来个图图或许更直观:

Go 的抽象语法树

这里我们以一个具体的例子来看:从 go 代码中提取所有结构体的名称。

// 源码
type A struct{}
type B struct{}
type C struct{}
func ExampleGetStructName() {
	fileSet := token.NewFileSet()
	node, err := parser.ParseFile(fileSet, "demo.go", nil, parser.ParseComments)
	if err != nil {
		return
	}

	ast.Inspect(node, func(n ast.Node) bool {
		if v, ok := n.(*ast.TypeSpec); ok {
			fmt.Println(v.Name.Name)
		}
		return true
	})
	// Output:
	// A
	// B
	// C
}

到此这篇关于以Golang为例详解AST抽象语法树的原理与实现的文章就介绍到这了,更多相关Go AST抽象语法树内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言中 Channel 详解

    Go语言中 Channel 详解

    Go 语言中的 channel 是实现 goroutine 间无锁通信的关键机制,他使得写多线程并发程序变得简单、灵活、触手可得。下面就个人理解对 channel 使用过程中应该注意的地方进行一个简要的总结。
    2018-10-10
  • 浅谈Go语言多态的实现与interface使用

    浅谈Go语言多态的实现与interface使用

    如果大家系统的学过C++、Java等语言以及面向对象的话,相信应该对多态不会陌生。多态是面向对象范畴当中经常使用并且非常好用的一个功能,它主要是用在强类型语言当中,像是Python这样的弱类型语言,变量的类型可以随意变化,也没有任何限制,其实区别不是很大
    2021-06-06
  • Golang官方限流器time/rate的使用与实现详解

    Golang官方限流器time/rate的使用与实现详解

    限流器是后台服务中十分重要的组件,在实际的业务场景中使用居多。time/rate 包基于令牌桶算法实现限流,本文主要为大家介绍了time/rate的使用与实现,需要的可以参考一下
    2023-04-04
  • golang调试bug及性能监控方式实践总结

    golang调试bug及性能监控方式实践总结

    这篇文章主要为大家介绍了golang调试bug及性能监控方式实践是总结,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • 详解Go语言中rand(随机数)包的使用

    详解Go语言中rand(随机数)包的使用

    在Golang中,有两个包提供了rand,分别为math/rand和crypto/rand对应两种应用场景。math/rand包实现了伪随机数生成器。也就是生成 整形和浮点型;crypto/rand包实现了用于加解密的更安全的随机数生成器。本文就来和大家详细讲讲math/rand的使用
    2022-08-08
  • Go语言中sync.Cond使用详解

    Go语言中sync.Cond使用详解

    本文主要介绍了Go语言中sync.Cond使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • Golang中漏洞数据库的使用详解

    Golang中漏洞数据库的使用详解

    govulncheck是Golang中的漏洞扫描工具,它强大功能的背后,离不开 Go 漏洞数据库(Go vulnerability database)的支持,所以本文就来为大家详细讲解下 Go 漏洞数据库相关的知识
    2023-09-09
  • Go语言中的switch用法实例分析

    Go语言中的switch用法实例分析

    这篇文章主要介绍了Go语言中的switch用法,实例分析了switch的功能及使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Go结构体从基础到应用深度探索

    Go结构体从基础到应用深度探索

    本文深入探讨了结构体的定义、类型、字面量表示和使用方法,旨在为读者呈现Go结构体的全面视角,通过结构体,开发者可以实现更加模块化、高效的代码设计,这篇文章旨在为您提供关于结构体的深入理解,助您更好地利用Go语言的强大功能
    2023-10-10
  • Go for-range 的 value值地址每次都一样的原因解析

    Go for-range 的 value值地址每次都一样的原因解析

    循环语句是一种常用的控制结构,在 Go 语言中,除了 for 关键字以外,还有一个 range 关键字,可以使用 for-range 循环迭代数组、切片、字符串、map 和 channel 这些数据类型,这篇文章主要介绍了Go for-range 的 value值地址每次都一样的原因解析,需要的朋友可以参考下
    2023-05-05

最新评论