前言

各位同行有没有想过一件事,一个程序文件,比如 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")
}

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

token literal(字面量,以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 抽象语法树的更多相关文章

  1. 从Babel开始认识AST抽象语法树

    前言 AST抽象语法树想必大家都有听过这个概念,但是不是只停留在听过这个层面呢.其实它对于编程来讲是一个非常重要的概念,当然也包括前端,在很多地方都能看见AST抽象语法树的影子,其中不乏有vue.re ...

  2. AST抽象语法树

    抽象语法树简介 (一)简介 抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并 ...

  3. AST抽象语法树 Javascript版

    在javascript世界中,你可以认为抽象语法树(AST)是最底层. 再往下,就是关于转换和编译的"黑魔法"领域了. 现在,我们拆解一个简单的add函数 function add ...

  4. 用python演示一个简单的AST(抽象语法树)

    如果对'a + 3 * b'进行解释,当中a=2,b=5 代码非常easy,就不再进行具体的解释了. Num = lambda env, n: n Var = lambda env, x: env[x ...

  5. 【深入】 - AST抽象语法树

    参考: https://segmentfault.com/a/1190000016231512

  6. 从零写一个编译器(九):语义分析之构造抽象语法树(AST)

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST) 抽象语法树(abstract syntax ...

  7. 如何查看SparkSQL 生成的抽象语法树?

    前言 在<Spark SQL内核剖析>书中4.3章节,谈到Catalyst体系中生成的抽象语法树的节点都是以Context来结尾,在ANLTR4以及生成的SqlBaseParser解析SQ ...

  8. Golang Context 包详解

    Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也 ...

  9. Go For Web:Golang http 包详解(源码剖析)

    前言: 本文作为解决如何通过 Golang 来编写 Web 应用这个问题的前瞻,对 Golang 中的 Web 基础部分进行一个简单的介绍.目前 Go 拥有成熟的 Http 处理包,所以我们去编写一个 ...

  10. JavaScript的工作原理:解析、抽象语法树(AST)+ 提升编译速度5个技巧

    这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! Jav ...

随机推荐

  1. SNN_SRM模型

    # SRM模型 ## 早期SRM模型 Spike Response Modul(SRM)模型将传统的LIF微分模型换成了一个关于输入.输出的脉冲函数,可以将脉冲神经网络简化为第二代神经网络. 基本公式 ...

  2. centos7安装glibc_2.28和gcc 8.2

    centos7默认的gcc版本是4.8.5,无法编译高版本的glibc 2.28,需要升级到gcc 8.2版本 注:gcc高版本和glibc 2.28不兼容 ## 查看自带默认的glibc strin ...

  3. vue-router重写push方法,解决相同路径跳转报错,解决点击菜单栏打开外部链接

    修改vue-router的配置文件,默认位置router/index.js import Vue from 'vue' import Router from 'vue-router' /** * 重写 ...

  4. Excel表格函数公式出现溢出怎么办?

    Excel是一款广泛使用的电子表格软件,它可以帮助我们进行各种计算.数据分析与处理等操作.在使用Excel时,我们通常需要使用到各种函数公式来完成不同的任务.然而,在使用函数公式时有时会出现" ...

  5. 【scikit-learn基础】--『数据加载』之玩具数据集

    机器学习的第一步是准备数据,好的数据能帮助我们加深对机器学习算法的理解. 不管是在学习还是实际工作中,准备数据永远是一个枯燥乏味的步骤.scikit-learn库显然看到了这个痛点,才在它的数据加载子 ...

  6. 技术Leader:像李云龙一样打造学习型团队

    今天跟大家分享一下怎么样构建一个学习型的团队. 首先对于计算机行业而言,不明而喻,我们要接受的东西真的太多了.我们接触的信息和变化也太多了.如果只是因循守旧,排斥新东西,那么我们被时代淘汰只是个时间问 ...

  7. 流媒体服务器ZLMediaKit与FFmpeg

    流媒体服务器ZLMediaKit与FFmpeg overview 关键字:ZLMediaKit.FFmpeg.srt.vlc 如果想快速拥有自己的流媒体服务器,那么可以使用开源项目自己搭建.开源的流媒 ...

  8. 组合式api-ref引用子组件、dom元素, defineExpose的使用

    和vue2一样,我们有时候希望父组件能够调用子组件中的方法.属性.那么就要用到ref. 然后你会发现,根本调用不了子组件中的方法"sonSayHi",如下图: 原因: 使用

  9. Javascript Ajax总结——跨域资源共享

    XHR对象只能访问与包含它的页面位于同一个中的资源.这种安全策略可以预防某些恶意行为.CORS(Cross-Origin Resource Sharing,跨域资源共享)是W3C的一个工作草案,定义了 ...

  10. Prometheus 监控告警系统搭建(对接飞书告警)

    Prometheus 是一套开源的系统监控报警框架,非常适合大规模集群的监控.它也是第二个加入CNCF的项目,受欢迎度仅次于 Kubernetes 的项目.本文讲解完整prometheus 监控和告警 ...