原文地址:https://blog.csdn.net/weixin_53009585/article/details/129236682
编译就是将源代码变成目标代码的过程。
如果源代码运行在操作系统上,那么目标代码是“汇编代码”。源代码通过汇编和链接的过程形成可执行文件,然后通过加载器将可执行文件加载到操作系统执行
如果源代码运行在虚拟机(解释器)上,那么目标代码是“解释器可以理解的中间形式的代码”,比如字节码(中间代码)IR、AST
词法分析将输入的字符串以单词符号的形式进行输出。
程序里的单词叫 Token,Token 的类型包括关键字、标识符、字面量、操作符等。
词法分析就是将字符串转换成 Token 序列的过程。
语法分析阶段将 Token 序列转换成体现语法规则的、树状数据结构,即抽象语法树 AST。AST 反应程序的语法结构,大多数编译器和解释器都使用 AST 作为源代码的内部表示。
语义分析阶段的任务是理解语义,语句要做什么。比如 + 执行加法、= 执行赋值、for 结构实现循环、if 结构实现判断。
语义分析阶段将执行上下文分析,包括引用消解、类型分析与检查等。
引用消解:找到变量所在的作用域,确定变量的作用范围属于全局,还是局部。
类型识别:比如执行 a + 3,需要识别出 a 的类型,因为浮点数和整数需要执行不同的运算方式。
类型检查:比如 int b = a + 3,是否可以进行赋值。仅当等号右边的表达式返回整型数据,或者能自动转换成整型的数据时,才能对类型为整型的 b 进行赋值。
int foo(int a) {
int b = a + 3;
return b;
}
比如上面的 C 代码,经过语义分析获得的信息(引用消解信息、类型信息),可以在 AST 上进行标注,形成“带有标注的语法树”,使编译器能更好地理解程序的语义。
这些上下文信息也将被存入“符号表”结构中,便于各阶段查询上下文信息。
符号表是有层次的结构:只需逐级向上查找就能找到变量、函数等信息(作用域、类型等)。
接下来可以解释执行,实现一门解释型语言。
提示
编译型语言需要生成目标代码,而解释型语言只需要解释器执行语义即可。
在语法分析后得到 AST,语义分析后得到“带标注的 AST”和符号表后,可以深度优先遍历 AST,并且边遍历边执行节点的语义规则。整个遍历过程就是执行代码的过程。
比如执行下面的语义:
遇到语法树中的 add “+” 节点:将两个字节点的值相加作为 “+” 节点的值。
遇到语法树中的变量节点(右值):取出变量的值。
遇到字面量:返回字面量代表的值。
在编译前端完成后,编译器可以直接解释执行,也可以生成目标代码。对于不同的 CPU 架构,还需要生成不同的汇编代码,如果对每种汇编代码都做优化,那么很繁琐。所以增加一个环节 - 生成中间代码 IR,然后统一优化 IR,再将 IR 转换成目标代码。
IR 有两个用途:
解释执行:解释型语言,比如 Python、Java,在生成 IR 后,即可直接执行。
优化代码:LLVM 等工具在生成代码后,需要做大量优化工作,并且很多优化工作没必要基于汇编代码来做(因为不同 CPU 体系的汇编代码不同),而可以基于 IR 使用统一的算法来完成,降低编译器适配不同 CPU 的复杂性。
源语言和目标语言有差异
程序员编写的代码不是最优的,编译器帮助纠正
基于 IR 编写优化算法的好处是可以将大部分优化算法写成与具体 CPU 架构无关的形式,从而降低编译器适配不同 CPU 的工作量。并且 LLVM 之类的工具可以使多种语言的前端生成相同的 IR,这样可以复用中/后端的程序。
目标代码生成就是生成虚拟机执行的字节码,或者操作系统执行的汇编代码。
目标代码生成的过程就是将 IR 逐个翻译成想要的汇编代码。目标代码生成阶段的任务包括:
选择合适的指令,生成性能最高的代码。
优化寄存器的分配,将频繁访问的变量(比如循环语句中的变量)放到寄存器中,因为寄存器比内存快。
在不改变运行结果的情况下,对指令做重排序优化,从而充分利用 CPU 内部的多个功能部件的并行能力。
目标代码生成后,整个编译过程完成。
原文地址:https://zhuanlan.zhihu.com/p/380421057
Go 官方提供如下包,实现 Go 代码的语法树分析:
go/scanner:词法分析,将源代码分割成 Token 序列
go/token:Token 类型以及相关结构体的定义
go/ast:AST 的结构体定义
go/parser:读取 Token 流生成 AST
抽象语法树由节点(Node)构成,从源代码中(go/ast/ast.go
)可以看出,Go AST 主要由三种节点构成:分别是表达式和类型节点(Expressions and type nodes)、语句节点(Statement nodes)和声明节点(Declaration nodes)。所有节点都包含标识其在源代码中开头和结尾位置的信息。
x
// ----------------------------------------------------------------------------
// Interfaces
//
// There are 3 main classes of nodes: Expressions and type nodes,
// statement nodes, and declaration nodes. The node names usually
// match the corresponding Go spec production names to which they
// correspond. The node fields correspond to the individual parts
// of the respective productions.
//
// All nodes contain position information marking the beginning of
// the corresponding source text segment; it is accessible via the
// Pos accessor method. Nodes may contain additional position info
// for language constructs where comments may be found between parts
// of the construct (typically any larger, parenthesized subpart).
// That position information is needed to properly position comments
// when printing the construct.
// All node types implement the Node interface.
type Node interface {
Pos() token.Pos // position of first character belonging to the node
End() token.Pos // position of first character immediately after the node
}
// All expression nodes implement the Expr interface.
type Expr interface {
Node
exprNode()
}
// All statement nodes implement the Stmt interface.
type Stmt interface {
Node
stmtNode()
}
// All declaration nodes implement the Decl interface.
type Decl interface {
Node
declNode()
}
从代码中可以看到,所有 AST Node 都实现 ast.Node
接口,它返回 Node 的位置信息。
另外,还有 3 个主要接口继承 ast.Node
:
ast.Expr
- 代表表达式和类型节点
ast.Stmt
- 代表语句节点
ast.Decl
- 代表声明节点
go/ast
中定义的全部节点类型如下图所示:
x
package main
import (
"go/ast"
"go/parser"
"go/token"
)
var srcCode = `
package hello
import "fmt"
func greet() {
var msg = "Hello World!"
fmt.Println(msg)
}
`
func main() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", srcCode, 0)
if err != nil {
panic(err.Error())
}
ast.Print(fset, f)
}
执行:
go run main.go
可以看到:
0 *ast.File {
1 . Package: 2:1
2 . Name: *ast.Ident {
3 . . NamePos: 2:9
4 . . Name: "hello"
5 . }
6 . Decls: []ast.Decl (len = 2) {
7 . . 0: *ast.GenDecl {
8 . . . TokPos: 4:1
9 . . . Tok: import
10 . . . Lparen: -
11 . . . Specs: []ast.Spec (len = 1) {
12 . . . . 0: *ast.ImportSpec {
13 . . . . . Path: *ast.BasicLit {
14 . . . . . . ValuePos: 4:8
15 . . . . . . Kind: STRING
16 . . . . . . Value: "\"fmt\""
17 . . . . . }
18 . . . . . EndPos: -
19 . . . . }
20 . . . }
21 . . . Rparen: -
22 . . }
23 . . 1: *ast.FuncDecl {
24 . . . Name: *ast.Ident {
25 . . . . NamePos: 6:6
26 . . . . Name: "greet"
27 . . . . Obj: *ast.Object {
28 . . . . . Kind: func
29 . . . . . Name: "greet"
30 . . . . . Decl: *(obj @ 23)
31 . . . . }
32 . . . }
33 . . . Type: *ast.FuncType {
34 . . . . Func: 6:1
35 . . . . Params: *ast.FieldList {
36 . . . . . Opening: 6:11
37 . . . . . Closing: 6:12
38 . . . . }
39 . . . }
40 . . . Body: *ast.BlockStmt {
41 . . . . Lbrace: 6:14
42 . . . . List: []ast.Stmt (len = 2) {
43 . . . . . 0: *ast.DeclStmt {
44 . . . . . . Decl: *ast.GenDecl {
45 . . . . . . . TokPos: 7:5
46 . . . . . . . Tok: var
47 . . . . . . . Lparen: -
48 . . . . . . . Specs: []ast.Spec (len = 1) {
49 . . . . . . . . 0: *ast.ValueSpec {
50 . . . . . . . . . Names: []*ast.Ident (len = 1) {
51 . . . . . . . . . . 0: *ast.Ident {
52 . . . . . . . . . . . NamePos: 7:9
53 . . . . . . . . . . . Name: "msg"
54 . . . . . . . . . . . Obj: *ast.Object {
55 . . . . . . . . . . . . Kind: var
56 . . . . . . . . . . . . Name: "msg"
57 . . . . . . . . . . . . Decl: *(obj @ 49)
58 . . . . . . . . . . . . Data: 0
59 . . . . . . . . . . . }
60 . . . . . . . . . . }
61 . . . . . . . . . }
62 . . . . . . . . . Values: []ast.Expr (len = 1) {
63 . . . . . . . . . . 0: *ast.BasicLit {
64 . . . . . . . . . . . ValuePos: 7:15
65 . . . . . . . . . . . Kind: STRING
66 . . . . . . . . . . . Value: "\"Hello World!\""
67 . . . . . . . . . . }
68 . . . . . . . . . }
69 . . . . . . . . }
70 . . . . . . . }
71 . . . . . . . Rparen: -
72 . . . . . . }
73 . . . . . }
74 . . . . . 1: *ast.ExprStmt {
75 . . . . . . X: *ast.CallExpr {
76 . . . . . . . Fun: *ast.SelectorExpr {
77 . . . . . . . . X: *ast.Ident {
78 . . . . . . . . . NamePos: 8:5
79 . . . . . . . . . Name: "fmt"
80 . . . . . . . . }
81 . . . . . . . . Sel: *ast.Ident {
82 . . . . . . . . . NamePos: 8:9
83 . . . . . . . . . Name: "Println"
84 . . . . . . . . }
85 . . . . . . . }
86 . . . . . . . Lparen: 8:16
87 . . . . . . . Args: []ast.Expr (len = 1) {
88 . . . . . . . . 0: *ast.Ident {
89 . . . . . . . . . NamePos: 8:17
90 . . . . . . . . . Name: "msg"
91 . . . . . . . . . Obj: *(obj @ 54)
92 . . . . . . . . }
93 . . . . . . . }
94 . . . . . . . Ellipsis: -
95 . . . . . . . Rparen: 8:20
96 . . . . . . }
97 . . . . . }
98 . . . . }
99 . . . . Rbrace: 9:1
100 . . . }
101 . . }
102 . }
103 . FileStart: 1:1
104 . FileEnd: 9:3
105 . Scope: *ast.Scope {
106 . . Objects: map[string]*ast.Object (len = 1) {
107 . . . "greet": *(obj @ 27)
108 . . }
109 . }
110 . Imports: []*ast.ImportSpec (len = 1) {
111 . . 0: *(obj @ 12)
112 . }
113 . Unresolved: []*ast.Ident (len = 1) {
114 . . 0: *(obj @ 77)
115 . }
116 }
x
// 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)
}
Inspect 以深度优先的方式遍历 AST:以调用 f(node) 开始;node 必须不为 nil。如果 f 返回 true,那么 Inspect 为该节点的每个非 nil 孩子递归地调用 f,后面跟着一个 f(nil) 调用。
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
var srcCode = `
package hello
import "fmt"
func greet() {
var msg = "Hello World!"
fmt.Println(msg)
}
`
func main() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", srcCode, 0)
if err != nil {
fmt.Printf("err = %s\n", err)
}
ast.Inspect(f, func(n ast.Node) bool {
// Called recursively.
ast.Print(fset, n)
return true
})
}
上述代码中的 AST 的大体结构如下:
xxxxxxxxxx
package main
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"log"
"os"
"reflect"
"strings"
)
func main() {
// 解析 Go 源码文件
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "interface_definition.go", nil, parser.ParseComments)
if err != nil {
log.Panicln(err.Error())
}
outFile, err := parser.ParseFile(fset, "out.go", "package "+file.Name.Name, parser.ParseComments)
// 遍历 AST,寻找接口
ast.Inspect(file, func(node ast.Node) bool {
if genDecl, ok := node.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE {
for _, spec := range genDecl.Specs {
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
if intf, ok := typeSpec.Type.(*ast.InterfaceType); ok {
GenInterfaceDefaultImpl(typeSpec.Name.Name, intf, outFile)
}
}
}
}
return true
})
if err := SaveASTToFile("out.go", fset, outFile); err != nil {
log.Panicln(err.Error())
}
}
// GenInterfaceDefaultImpl 生成接口的默认实现
func GenInterfaceDefaultImpl(name string, intf *ast.InterfaceType, file *ast.File) {
structName := name + "DefaultImpl"
// 创建结构体的类型声明
typeSpec := &ast.TypeSpec{
Name: ast.NewIdent(structName),
Type: &ast.StructType{
Fields: &ast.FieldList{},
},
}
// 创建结构体的声明
decl := &ast.GenDecl{
Tok: token.TYPE,
Specs: []ast.Spec{typeSpec},
}
// 将结构体的声明添加到文件的声明列表中
file.Decls = append(file.Decls, decl)
for _, method := range intf.Methods.List {
// 方法名称
var methodName string
for _, name := range method.Names {
methodName = name.Name
}
// TODO:支持接口继承
if methodName == "" {
log.Panicln("no method name found")
}
methodType, ok := method.Type.(*ast.FuncType)
if !ok {
continue
}
// 创建方法的接收者
recv := &ast.FieldList{
List: []*ast.Field{
{
Names: []*ast.Ident{ast.NewIdent(GetFirstLower(structName))},
Type: &ast.StarExpr{X: ast.NewIdent(structName)},
},
},
}
// 创建方法的参数
params := &ast.FieldList{
List: methodType.Params.List,
}
// 创建方法的输出结果
results := &ast.FieldList{
List: nil,
}
if methodType.Results != nil {
results = &ast.FieldList{
List: methodType.Results.List,
}
}
// 创建方法的函数体
body := &ast.BlockStmt{
List: []ast.Stmt{
CreateReturnZeroStmt(results),
},
}
logArgsStmts := CreateLogArgsStatements(params)
// 在函数体的开头插入打印参数的语句
body.List = append(logArgsStmts, body.List...)
// 创建方法声明
funcDecl := &ast.FuncDecl{
Name: ast.NewIdent(methodName),
Recv: recv,
Type: &ast.FuncType{
Params: params,
Results: results,
},
Body: body,
}
// 将方法的声明添加到文件的声明列表中
file.Decls = append(file.Decls, funcDecl)
// 导入使用的包
if !HasImportedPackage(file, "log") {
AddImport(file, "log")
}
}
}
// CreateReturnZeroStmt 创建返回零值的语句
func CreateReturnZeroStmt(results *ast.FieldList) *ast.ReturnStmt {
// 创建返回值列表
var resultsExpr []ast.Expr
for _, field := range results.List {
switch ident := reflect.ValueOf(field.Type).Elem().Interface().(type) {
case ast.Ident:
resultsExpr = append(resultsExpr, CreateZeroExpr(ident.Name))
default:
resultsExpr = append(resultsExpr, &ast.Ident{Name: "nil"})
}
}
// 创建返回语句
return &ast.ReturnStmt{Results: resultsExpr}
}
// CreateZeroExpr 创建零值的 AST 表达式
func CreateZeroExpr(name string) ast.Expr {
switch name {
case "bool":
return &ast.Ident{Name: "false"}
case "int", "int8", "int16", "int32", "int64":
return &ast.BasicLit{Kind: token.INT, Value: "0"}
case "uint", "uint8", "uint16", "uint32", "uint64", "uintptr":
return &ast.BasicLit{Kind: token.INT, Value: "0"}
case "float32", "float64":
return &ast.BasicLit{Kind: token.FLOAT, Value: "0.0"}
case "complex64", "complex128":
return &ast.BasicLit{Kind: token.IMAG, Value: "0.0i"}
case "string":
return &ast.BasicLit{Kind: token.STRING, Value: `""`}
default:
return &ast.Ident{Name: "nil"}
}
}
// HasImportedPackage 检查是否已导入指定的包
func HasImportedPackage(file *ast.File, packageName string) bool {
for _, imp := range file.Imports {
if imp.Path.Value == fmt.Sprintf(`"%s"`, packageName) {
return true
}
}
return false
}
// AddImport 添加导入语句
func AddImport(file *ast.File, packageName string) {
// 创建导入的 AST 节点
importSpec := &ast.ImportSpec{
Path: &ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf(`"%s"`, packageName),
},
}
// 创建导入声明
importDecl := &ast.GenDecl{
Tok: token.IMPORT,
Specs: []ast.Spec{importSpec},
}
file.Imports = append(file.Imports, importSpec)
// 将导入声明添加到文件的声明列表
file.Decls = append([]ast.Decl{importDecl}, file.Decls...)
}
// CreatePrintStatement 创建打印语句
func CreatePrintStatement(paramName string) ast.Stmt {
// log.Println
printFunc := &ast.SelectorExpr{
X: ast.NewIdent("log"),
Sel: ast.NewIdent("Println"),
}
// "<ParamName> = ",比如 "name = "
paramNameString := &ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf(`"%s = "`, paramName),
}
// <ParamName>,比如 name
paramNameExpr := ast.NewIdent(paramName)
// log.Println("<ParamName> = ", <ParamName>),比如 log.Println("name = ", name)
callExpr := &ast.CallExpr{
Fun: printFunc,
Args: []ast.Expr{paramNameString, paramNameExpr},
}
return &ast.ExprStmt{
X: callExpr,
}
}
// CreateLogArgsStatements 创建打印参数的语句
func CreateLogArgsStatements(params *ast.FieldList) []ast.Stmt {
stmts := make([]ast.Stmt, 0)
for _, param := range params.List {
for _, name := range param.Names {
printStmt := CreatePrintStatement(name.Name)
stmts = append(stmts, printStmt)
}
}
return stmts
}
// SaveASTToFile 将格式化后的代码保存到文件
func SaveASTToFile(fileName string, fset *token.FileSet, node *ast.File) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
defer file.Close()
// 格式化代码
var buf bytes.Buffer
err = format.Node(&buf, fset, node)
if err != nil {
return err
}
// 将结果写入文件
_, err = file.WriteString(buf.String())
return err
}
// GetFirstLower 首先获取给定的字符串的首字母,然后将其转成小写字母
func GetFirstLower(s string) string {
if len(s) == 0 {
return s
}
return strings.ToLower(string(s[0]))
}
go generate
命令go generate
是 Go 语言提供的命令,用于自动化生成代码。通过在代码中添加特定的注释,可以触发 go generate
命令执行预定义的代码生成操作。这样可以简化重复性的任务,比如生成接口的实现、生成模拟数据、生成文档等。
go generate
的使用步骤如下:
在源代码中添加 //go:generate
注释,指定要执行的命令。该注释必须在独立的行上
运行 go generate
命令,它将扫描代码中的 //go:generate
注释,并且执行其中指定的命令
以下是展示如何使用 go generate
的简单示例:
xxxxxxxxxx
package main
//go:generate echo "Hello, Go generate!"
func main() {
// ...
}
注意事项:
需要在包的根目录下运行 go generate
命令
注释中的命令可以是任意可执行命令,比如 Shell 脚本、Go 程序等
可以在一个文件中添加多个 //go:generate
注释,go generate
将按顺序执行它们
可以结合其它工具和框架一起使用 go generate
命令,比如与 protoc
一起使用,生成 .pb.go
文件