原文地址: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:12 . Name: *ast.Ident {3 . . NamePos: 2:94 . . Name: "hello"5 . }6 . Decls: []ast.Decl (len = 2) {7 . . 0: *ast.GenDecl {8 . . . TokPos: 4:19 . . . Tok: import10 . . . Lparen: -11 . . . Specs: []ast.Spec (len = 1) {12 . . . . 0: *ast.ImportSpec {13 . . . . . Path: *ast.BasicLit {14 . . . . . . ValuePos: 4:815 . . . . . . Kind: STRING16 . . . . . . Value: "\"fmt\""17 . . . . . }18 . . . . . EndPos: -19 . . . . }20 . . . }21 . . . Rparen: -22 . . }23 . . 1: *ast.FuncDecl {24 . . . Name: *ast.Ident {25 . . . . NamePos: 6:626 . . . . Name: "greet"27 . . . . Obj: *ast.Object {28 . . . . . Kind: func29 . . . . . Name: "greet"30 . . . . . Decl: *(obj @ 23)31 . . . . }32 . . . }33 . . . Type: *ast.FuncType {34 . . . . Func: 6:135 . . . . Params: *ast.FieldList {36 . . . . . Opening: 6:1137 . . . . . Closing: 6:1238 . . . . }39 . . . }40 . . . Body: *ast.BlockStmt {41 . . . . Lbrace: 6:1442 . . . . List: []ast.Stmt (len = 2) {43 . . . . . 0: *ast.DeclStmt {44 . . . . . . Decl: *ast.GenDecl {45 . . . . . . . TokPos: 7:546 . . . . . . . Tok: var47 . . . . . . . Lparen: -48 . . . . . . . Specs: []ast.Spec (len = 1) {49 . . . . . . . . 0: *ast.ValueSpec {50 . . . . . . . . . Names: []*ast.Ident (len = 1) {51 . . . . . . . . . . 0: *ast.Ident {52 . . . . . . . . . . . NamePos: 7:953 . . . . . . . . . . . Name: "msg"54 . . . . . . . . . . . Obj: *ast.Object {55 . . . . . . . . . . . . Kind: var56 . . . . . . . . . . . . Name: "msg"57 . . . . . . . . . . . . Decl: *(obj @ 49)58 . . . . . . . . . . . . Data: 059 . . . . . . . . . . . }60 . . . . . . . . . . }61 . . . . . . . . . }62 . . . . . . . . . Values: []ast.Expr (len = 1) {63 . . . . . . . . . . 0: *ast.BasicLit {64 . . . . . . . . . . . ValuePos: 7:1565 . . . . . . . . . . . Kind: STRING66 . . . . . . . . . . . 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:579 . . . . . . . . . Name: "fmt"80 . . . . . . . . }81 . . . . . . . . Sel: *ast.Ident {82 . . . . . . . . . NamePos: 8:983 . . . . . . . . . Name: "Println"84 . . . . . . . . }85 . . . . . . . }86 . . . . . . . Lparen: 8:1687 . . . . . . . Args: []ast.Expr (len = 1) {88 . . . . . . . . 0: *ast.Ident {89 . . . . . . . . . NamePos: 8:1790 . . . . . . . . . Name: "msg"91 . . . . . . . . . Obj: *(obj @ 54)92 . . . . . . . . }93 . . . . . . . }94 . . . . . . . Ellipsis: -95 . . . . . . . Rparen: 8:2096 . . . . . . }97 . . . . . }98 . . . . }99 . . . . Rbrace: 9:1100 . . . }101 . . }102 . }103 . FileStart: 1:1104 . FileEnd: 9:3105 . 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 的大体结构如下:

xxxxxxxxxxpackage 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 的简单示例:
xxxxxxxxxxpackage main
//go:generate echo "Hello, Go generate!"
func main() { // ...}注意事项:
需要在包的根目录下运行 go generate 命令
注释中的命令可以是任意可执行命令,比如 Shell 脚本、Go 程序等
可以在一个文件中添加多个 //go:generate 注释,go generate 将按顺序执行它们
可以结合其它工具和框架一起使用 go generate 命令,比如与 protoc 一起使用,生成 .pb.go 文件