编写go语言用到的编译器
by Joseph Livni
约瑟夫·利夫尼(Joseph Livni)
如何在Go中编写编译器:快速指南 (How to write a compiler in Go: a quick guide)
Compilers are awesome! ? ? ? They combine theory and application and touch on a lot of software related topics such as parsing and language construction. At their core, compilers are a program that make a program readable by the computer.
编译器很棒! ? ? ? 他们将理论和应用程序结合在一起,并涉及许多与软件相关的主题,例如解析和语言构造。 编译器的核心是使计算机可读的程序。
The inspiration for this came out of a compilers course I took this past Fall and my love for Go.
灵感来自于我去年秋天修读的编译器课程以及我对Go的热爱。
This is the guide I wish I had when starting my journey into compilers. There are a lot of books, videos, and tutorials on how to create compilers. The goal of this post is to strike a balance between providing a nontrivial example of some of the things a compiler can do while avoid getting stuck in the weeds. ?
这是我入门编译器时希望得到的指南。 有很多关于如何创建编译器的书籍,视频和教程。 这篇文章的目的是在提供一个简单的例子来说明编译器在避免陷入困境的同时可以做的一些事情之间取得平衡。 ?
The result will be a compiler that can execute a small made up language.To checkout and run the final project see the instructions below. ?
结果将是可以执行一种小型组合语言的编译器。要签出并运行最终项目,请参见以下说明。 ?
Note: Remember that Go is strict about absolute paths when running this
注意:请记住,运行此命令时Go严格遵守绝对路径
cd $GOPATH/src/github.com/Lebonescogit clone https://github.com/Lebonesco/go-compiler.gitcd go-compilergo test -vgo run main.go ./examples/math.bx
编译器概述 (Outline of the Compiler)
Lexer/Parser
词法分析器/解析器
AST Generator
AST发电机
Type Checker
类型检查器
Code Generation
代码生成
语言 (The Language)
The goal of this post is to get you familiar with compilers as quickly as a possible so we’ll keep the language simple. For Types we’ll work with strings
, integers
, and bools
. It will have Statements that include func
, if
, else
, let
, and return
. This should be enough to have fun working with some of the complexities of a compiler.
这篇文章的目的是使您尽快熟悉编译器,以便我们使语言保持简单。 对于Types,我们将使用strings
, integers
和bools
。 if
不包含, if
包含func
语句 , else
, let
并return
。 这应该足以使您有趣地使用一些编译器。
The first compiler that I built, I completed over the course of two months and took up 1000’s of lines of code. I took some shortcuts in this post in order to show you the key fundamentals.
我构建的第一个编译器在两个月的时间内完成了工作,并占用了1000行代码。 为了使您了解关键的基础知识,我在本文中采取了一些捷径。
Two common components that our language is missing are classes
and arrays
. These add additional complications we don’t have time for right now. If it turns out that people really want to know how to handle these elements I’ll write a followup.
我们的语言缺少的两个常见组件是classes
和arrays
。 这些增加了我们现在没有时间的其他复杂性。 如果事实证明人们真的想知道如何处理这些元素,那么我将写一篇后续文章。
Some example code:
一些示例代码:
func add(a Int, b int) Int { return a + b;}
func hello(name String) String { return "hello:" + " " + name;}
let num = add(1, 2);let phrase = string hello("Jeff");let i = int 0;let result = "";
if (i == 2) { result = hello("cat");} else { result = hello("dog");}
PRINT(result);
快速设置 (Quick Setup)
The only outside package we need is gocc
, which will help build the lexer and parser.
我们唯一需要的外部软件包是gocc
,它将帮助构建词法分析器和解析器。
To get it run:
要使其运行:
go get github.com/goccmack/gocc
Make sure the bin folder where gocc is located is in your PATH
:
确保gocc所在的bin文件夹在您的PATH
:
export PATH=$GOPATH/bin:$PATH
Note: If you’re having problems at this stage try running go env
to make sure that your $GOROOT
and $GOPATH
are correctly assigned.
注意:如果在此阶段遇到问题,请尝试运行go env
以确保正确分配了$GOROOT
和$GOPATH
。
Cool, let’s dive into some code.
太酷了,让我们深入研究一些代码。
构建词法分析器 (Building the Lexer)
The lexer’s job is to read the program and output a stream of tokens that are consumed by the parser. Each Token
contains the type
that the token represents in the language and the string Literal
of that token.
词法分析器的工作是读取程序并输出解析器使用的令牌流。 每个Token
包含令牌用该语言表示的type
以及该令牌的字符串Literal
。
To identify the pieces of the program we will be using regular expressions. gocc will then convert these regular expressions into a DFA (Deterministic Finite Automata) which can theoretically run in linear time.
为了识别程序的各个部分,我们将使用正则表达式。 然后,gocc会将这些正则表达式转换为DFA ( 确定性有限自动机 ),该DFA理论上可以在线性时间内运行。
The notation that we’ll be using is BNF (Backus–Naur form). Don’t confuse this with EBNF (extended Backus–Naur form) or ABNF (augmented Backus–Naur form) which have some added features. Keep this in mind when looking at other examples online that could be using other forms which provide more syntactic sugar.
我们将使用的表示法是BNF ( Backus–Naur形式 )。 不要将此与具有某些附加功能的EBNF ( 扩展Backus–Naur形式 )或ABNF ( 增强Backus–Naur形式 )相混淆。 在网上查看其他示例时,请牢记这一点,这些示例可能正在使用提供更多语法糖的其他形式。
Let’s start with the basics and define what strings
and integers
will look like.
让我们从基础开始,定义什么样的strings
和integers
。
Note that:
注意:
- All token names must be lower case 所有令牌名称必须小写
- Any key preceded by ‘!’ will be ignored 任何以“!”开头的键 将被忽略
- Any key preceded by ‘_’ will not by turned into a token 任何以“ _”开头的键都不会变成令牌
Any expression enclosed by ‘{‘
expression
‘}’ can be repeated 0 or more times'{'
expression
'}'内的任何表达式都可以重复0次或更多次
In the below example we are ignoring all white space and have defined an int
and string_literal
token
.
在下面的示例中,我们忽略了所有空格,并定义了一个int
和string_literal
token
。
Every language has built in keywords
that are reserved words that deliver special functionality. But, how will the lexer know whether a string
is a keyword
or a user created identifier
? It handles this be giving preference to the order in which tokens are defined. Therefore, let’s define keywords
next.
每种语言都有内置的keywords
,这些keywords
是保留字词,可提供特殊功能。 但是,词法分析器如何知道string
是keyword
还是用户创建的identifier
? 它通过优先考虑定义令牌的顺序来进行处理。 因此,让我们接下来定义keywords
。
We’ll finish this up by adding the punctuation necessary for expressions.
我们将通过添加表达式所需的标点符号来完成此操作。
Cool! Let’s see if this is actually doing what we want with some unit tests. Feel free to just paste this part into your IDE. ?
凉! 让我们看看这是否确实在完成一些单元测试所需的工作 。 随意将这部分粘贴到您的IDE中。 ?
Note: It’s generally good practice in Go to place test files in the relevant subdirectory, but for simplicity I’m placing all tests in the root.
注意:在Go中将测试文件放置在相关的子目录中通常是一个好习惯,但是为了简单起见,我将所有测试都放置在根目录中。
To test our scanner run:
要测试我们的扫描仪运行:
$ gocc grammer.bnf$ go test -v=== RUN TestToken--- PASS: TestToken (0.00s)PASSok github.com/Lebonesco/compiler 0.364s
Great, we now have a working Lexer
?
太好了,我们现在有一个工作的Lexer
吗?
构建解析器 (Building the Parser)
Building the Parser
is similar to how we built the Lexer
. We will construct a set of element sequences that when correctly match a stream of tokens produce a grammar. This will also run in linear time by internally converting our NFA (Non-Deterministic Automaton) grammar to DFA (Deterministic Finite Automaton).
构建Parser
类似于我们构建Lexer
。 我们将构建一组元素序列,当正确匹配令牌流时,它们会产生语法。 通过将我们的NFA ( 非确定性自动机 )语法内部转换为DFA ( 确定性有限自动机 ),这也将在线性时间内运行。
Let’s keep things simple. What actually is our program? Well, it’s a bunch of Statements
in which each Statement
can contain a set of Statements
and/or Expressions
. This seems like a good place to start our grammar.
让我们保持简单。 我们的程序到底是什么? 嗯,这是一堆Statements
,其中每个Statement
可以包含一组Statements
和/或Expressions
。 这似乎是开始我们的语法的好地方。
Below is the beginning recursive hierarchy of the program. Statements
is a sequence of zero or more Statements
and Functions
is a list of functions. Our languages requires functions to be defined before other Statement
types. This will reduce some headache during the type checking phase. empty
is a keyword in BNF that represents an empty space.
下面是程序的开始递归层次结构。 Statements
是零个或多个Statements
的序列。 Functions
是Functions
的列表。 我们的语言要求在其他Statement
类型之前定义函数。 这将减少类型检查阶段的麻烦。 empty
是BNF中的关键字,表示空白。
But wait! What is a Statement
? It’s a unit of code that doesn’t return a value. This includes: if
, let
, and return
statements. This is opposed to Expressions
which do return values. We will get to those next.
可是等等! 什么是Statement
? 它是不返回值的代码单元。 这包括: if
, let
和return
语句。 这与确实返回值的Expressions
相反。 我们接下来将介绍这些内容。
Below is our grammar for Statement
and Function
. A StatementBlock
is a larger abstraction that encapsulates a list of Statements
with braces {
}
.
下面是Statement
和Function
语法。 StatementBlock
是一个较大的抽象,它用大括号{
}
封装了一系列Statements
。
Next lets build out our Expression
which handles all infix operations such as +
, -
, *
, &
lt;
, &g
t;, =
=, and,
and or.
接下来,让我们构建处理所有中缀操作(例如+
, -
, *
&
lt)的Expression
;
, &g
t; , =
=和,
和或。
Almost done with a fully working grammar! Let’s wrap things up by defining our parameter insertion. Each function
can accept any amount of values. When defining a function we need to label the argument types in the signature while a called function can accept any amount of Expressions
.
几乎完全可以使用语法了! 让我们通过定义参数插入来总结一下。 每个function
可以接受任意数量的值。 定义函数时,我们需要在签名中标记参数类型,而被调用函数可以接受任意数量的Expressions
。
Now that our parser is completed let’s add some code to our driver, main.go
.
现在我们的解析器已经完成,让我们向驱动程序main.go
添加一些代码。
As we progress through the compiler we will add more functionality to this driver.
随着编译器的进行,我们将向该驱动程序添加更多功能。
One of the things challenging about building a parser is that there’re many different ways to define the grammar. ?
构建解析器所面临的挑战之一是,有许多不同的方法来定义语法。 ?
We won’t really know how well we did until we get into the next section which uses the output we just generated. The difficulty of building the static type checker will be strongly influenced by our grammar design. Keep your fingers crossed ?.
在进入下一部分使用刚刚生成的输出的部分之前,我们不会真正知道我们的性能如何。 构建静态类型检查器的难度将受到我们语法设计的强烈影响。 手指交叉吗?
测试解析器 (Test Parser)
We’ll keep this simple because at this point our parser can still produce false positives. Once we start working on the AST we can check its accuracy.
我们将保持简单,因为此时解析器仍会产生误报。 一旦我们开始研究AST,我们就可以检查其准确性。
go test -v=== RUN TestParser--- PASS: TestParser (0.00s)=== RUN TestToken--- PASS: TestToken (0.00s)PASSok github.com/Lebonesco/go-compiler 7.295s
Congrats ? ? ?! You now have a fully working Lexer and Parser. Time to move onto the AST (Abstract Syntax Tree).
恭喜 ? ? ?! 现在,您已经可以正常使用Lexer和Parser。 是时候进入AST (抽象语法树)了。
抽象语法树 (Abstract Syntax Tree)
The best way to understand an abstract syntax tree is in relation to a parse tree which is what we generated in the last post. A parse tree represents each part of the program that is matched in our grammar.
理解抽象语法树的最佳方法是与解析树相关,解析树是我们在上一篇文章中生成的。 语法分析树表示程序中与我们的语法匹配的每个部分。
By contrast, an AST only contains the information related to type checking and code generation, and skips any other extra content that is used while parsing the text.
相比之下,AST仅包含与类型检查和代码生成有关的信息,并且跳过解析文本时使用的任何其他额外内容。
Don’t worry if that definition doesn’t makes sense right now. I always learn best by actually coding, so let’s jump into it!
如果该定义现在没有意义,请不要担心。 我总是通过实际编码学习得最好,所以让我们开始吧!
Create a new directory and two new files. ast.go
will contain our AST generating functions and types.go
will have the tree node types.
创建一个新目录和两个新文件。 ast.go
将包含我们的AST生成函数和types.go
将具有树节点类型 。
mkdir astcd asttouch ast.gotouch types.go
Like with the parse tree, we will define our structure from top to bottom. Every node
will either be a Statement
or Expression
. Go isn’t object oriented so we’ll use a composition pattern utilizing interface
and struct
to represent our node
categories. Our AST will return a Program
node that contains the rest of the program. From now on, any struct we created with a TokenLiteral()
method is a node
. If that node
has a statementNode()
method it will fit the Statement
interface and if it has a expressionNode()
method it’s an Expression
.
像解析树一样,我们将从上到下定义我们的结构。 每个node
将是一个Statement
或Expression
。 Go不是面向对象的,因此我们将使用通过interface
和struct
表示node
类别的合成模式。 我们的AST将返回一个包含该程序其余部分的“程序Program
节点。 从现在开始,我们使用TokenLiteral()
方法创建的任何结构都是一个node
。 如果该node
具有statementNode()
方法,则它将适合Statement
接口;如果它具有expressionNode()
方法,则它是一个Expression
。
In addition, we’ll be adding json
tags to each struct field to make it easier when we convert our AST into json
for testing purposes.
另外,我们将在每个struct字段中添加json
标记,以简化将AST转换为json
以进行测试时的操作。
Now let’s start building our Statement
structs based off of the Statement
and Node
interfaces.
现在,让我们开始基于Statement
和Node
接口构建Statement
结构。
Note: json:"-"
means that the field will be omitted from our json.
注意: json:"-"
表示该字段将从json中省略。
Next we need some hooks to connect our nodes
and parser
. The code below allows our Statement
nodes to fit the Node
and Statement
interfaces.
接下来,我们需要一些钩子来连接nodes
和parser
。 下面的代码允许我们的Statement
节点适合Node
和Statement
接口。
We then need the hooks that will be called by the parser.
然后,我们需要解析器将调用的钩子。
As you can see, most of our code is checking and casting our input type.
如您所见, 我们的大多数代码都是检查并转换我们的输入类型。
These hooks will then be called by the parser in grammar.bnf
. To access these functions we’ll need to import "github.com/Lebonesco/go-compiler/ast
.
然后,解析器将在grammar.bnf
调用这些挂钩。 要访问这些功能,我们需要import "github.com/Lebonesco/go-compiler/ast
。
Now when a piece of grammar is identified, it calls an AST hook and passes in the necessary data to construct a node
.
现在,当识别出一条语法时,它会调用AST钩子,并传递必要的数据以构造一个node
。
As you could imagine, there is a lot of flexibility in how you want to generate your AST. There are design strategies that reduce the amount of unique nodes in the AST . However, having more node types will make your life easier when we get to the typechecker
and code generation
. ?
可以想象,在生成AST的方式上有很多灵活性。 有一些设计策略可以减少AST中唯一节点的数量。 然而,有更多的节点类型将使您的生活更轻松,当我们到了typechecker
和code generation
。 ?
This part has a lot of code. However, it’s not very difficult and mostly all the same. The error handling in Go can feel a bit tedious, but in the long run it’ll be worth it when we make a silly mistake. Safety First ?
这部分有很多代码。 但是,这并不是很难,而且几乎都一样。 Go中的错误处理可能会有些乏味,但是从长远来看,当我们犯了一个愚蠢的错误时,这是值得的。 安全第一 ?
We’re not going to do anything too crazy with our error handling because I want to save on lines of code. However, if you feel so inclined you can add more descriptive and useful errors.
我们不会对错误处理做任何疯狂的事情,因为我想节省代码行。 但是,如果您倾向于这样做,则可以添加更多描述性和有用的错误。
Let’s move on to Expressions
!
让我们继续进行Expressions
!
Fit them into the Node
and Expression
interfaces.
使它们适合Node
和Expression
接口。
And create the Expression
hooks.
并创建Expression
挂钩。
Finally, insert the hooks into the parser
.
最后,将钩子插入parser
。
测试AST发生器 (Test AST Generator)
The test cases for the AST generator are the most tedious to write. But trust me, this is not a part you want to mess up on. If you have problems here, those bugs will rollover into your type checker
and code generator
. ?
AST生成器的测试用例编写起来最繁琐。 但是请相信我,这不是您想要弄混的部分。 如果您在此处遇到问题,则这些错误将累积到type checker
和code generator
。 ?
In my opinion, if code doesn’t have tests it’s broken
我认为,如果代码没有测试,那就坏了
In our AST test we will construct what our final result should look like. To avoid comparing elements such as tokens
, that could create false negatives, we convert our result and expected output into json before comparing with a deep comparison function, reflect.DeepEqual()
.
在AST测试中,我们将构造最终结果。 为了避免比较可能会产生假阴性的诸如tokens
元素,在与深度比较函数reflect.DeepEqual()
比较之前,我们将结果和预期输出转换为json。
Run Test:
运行测试:
go test -v=== RUN TestAST--- PASS: TestAST (0.00s)=== RUN TestParser--- PASS: TestParser (0.00s)=== RUN TestToken--- PASS: TestToken (0.00s)PASSok github.com/Lebonesco/go-compiler 9.020s
Half way to a working compiler! ? While you give this post some ? ? ? don’t forget to give yourself a hand for making it this far.
正常工作的编译器的一半! ? 你给这篇文章一些吗? ? ? 别忘了帮助自己做到这一点。
Let’s add some more code to the driver.
让我们向驱动程序添加更多代码。
Now onto my favorite part, the Type Checker.
现在到我最喜欢的部分, 类型检查器 。
类型检查器 (Type Checker)
Our type checker will make sure that users don’t write code that conflicts with our statically typed language.
我们的类型检查器将确保用户不会编写与我们的静态类型化语言冲突的代码。
The core backbone of our type checker will be a combination of data structures that track identifier types, initialization, and valid type operations. This can get vastly more complicated once we start dealing with different levels of scope and classes. However, we’re keeping it as simple as possible. ?
类型检查器的核心骨干将是跟踪标识符类型,初始化和有效类型操作的数据结构的组合。 一旦我们开始处理不同级别的范围和类,这将变得非常复杂。 但是,我们将其保持尽可能简单。 ?
To start:
开始:
touch checker_test.gomkdir checkercd checkertouch checker.gotouch environment.go
environment.go
will contain all of our helper functions that will be used by our checker and code generator to access and manipulate our set of variables and corresponding types. Our environment is simple so this will be straight forward.
environment.go
将包含我们的所有辅助函数,我们的检查器和代码生成 器将使用这些函数来访问和操纵我们的变量集和相应类型。 我们的环境很简单,所以这很简单。
We’ll begin by setting all of our constant values including operation types, variable types, and mapping of each type to its valid methods.
我们将从设置所有常量值开始,包括操作类型 , 变量类型以及每种类型到其有效方法的映射 。
Note: If we had classes in our language our checker would handle this third part rather than us doing it by hand.
注意:如果我们有使用我们的语言编写的类,则检查器将处理此第三部分,而不是我们手工完成。
The rest of environment.go
are basic getters and setters that handle identifiers and functions.
其余environment.go
是基本的getter和setter该句柄标识和功能。
The foundation of our type checker will be a single dispatch function, checker()
, that takes in a Node
and fires the corresponding checker function
类型检查器的基础是单个调度函数checker()
,该函数接收一个Node
并触发相应的检查器函数
I felt like saving lines of code so we’ll be using a global environment to store our variable types.
我感觉很想节省代码行,因此我们将使用全局环境来存储变量类型。
Note: This wouldn’t be possible if we allowed Block Statements
and Functions
to have their own scope or if we were abiding by best practices.
注意:如果我们允许Block Statements
和Functions
具有自己的范围,或者我们遵循最佳实践,则这是不可能的。
Now eval Statements
. Block Statements
are the only statement in which we return a type in order to handle the case when it is inside a function. If there is a Return Statement
inside the Block Statement
its type is returned. Otherwise, the Nothing_Type
is returned.
现在评估Statements
。 Block Statements
是我们返回类型的唯一语句,以便处理在函数内部的情况。 如果Block Statement
有Return Statement
,则返回其类型。 否则,将返回Nothing_Type
。
Onto evaluating Expressions
. The most complicated part will be evalFunctionCall()
because it could either be a built in function such as PRINT()
or user defined.
评估Expressions
。 最复杂的部分是evalFunctionCall()
因为它可以是内置函数(如PRINT()
或用户定义的。
Note: Currently, there is only one builtin function. However, more could be easily added given the framework that we’ve built.
注意:当前,只有一个内置函数。 但是,鉴于我们已经建立的框架,可以轻松添加更多内容。
Awesome! Let’s add a small snippet to our driver.
太棒了! 让我们向驱动程序添加一个小片段。
测试类型检查器 (Test Type Checker)
go test -v=== RUN TestAST--- PASS: TestAST (0.00s)=== RUN TestOperations--- PASS: TestOperations (0.00s)=== RUN TestIdents--- PASS: TestIdents (0.00s)=== RUN TestFunctions--- PASS: TestFunctions (0.00s)=== RUN TestParser--- PASS: TestParser (0.00s)=== RUN TestToken--- PASS: TestToken (0.00s)PASSok github.com/Lebonesco/go-compiler 9.020s
I made some deliberate choices to leave things out of this language such as classes
, for loops
, and function scope
. Most of the complexities that arise from these occur in the checker
and code generator
. If I added those elements this post would be a lot lot longer. ?
我做出了一些有选择的选择,以使诸如此类, for loops
和函数scope
classes
东西不被使用。 由此产生的大多数复杂性都发生在checker
和code generator
。 如果我添加这些元素,那么这篇文章将长很多。 ?
代码生成 (Code Generation)
We have finally made it to the last stage! ? ? ?
我们终于到了最后阶段! ? ? ?
In order to handle the most cases with the least amount of hassle every expression
will be assigned to a temporary variable. It might make our generated code look bloated, but it will solve for any nested expressions.
为了以最少的麻烦处理大多数情况,每个expression
都会分配给一个临时变量。 它可能会使我们生成的代码显得过大,但是它将解决所有嵌套表达式。
Bloated code won’t have any impact on final program speed because the optimizer will remove any redundancy when we compile our final generated C++ code.
膨胀的代码不会对最终程序速度产生任何影响,因为当我们编译最终生成的C ++代码时,优化器将消除任何冗余。
Our generator will look similar to the type checker. The main difference is that we’ll be passing down a buffer
to store the generated code.
我们的生成器看起来类似于类型检查器。 主要区别在于我们将向下传递一个buffer
来存储生成的代码。
While I chose to compile into C++, you can substitute in any language . The main purpose of this Go Compiler Guide was to enable you to be able to understand the pieces well enough to go out and create your own.
当我选择编译成C ++时,您可以用任何语言替代。 这份《 Go Compiler Guide 》的主要目的是使您能够充分理解各个部分,以便自行创建。
To start:
开始:
touch gen_test.gomkdir gencd gentouch gen.go
We’ll begin by importing all of the necessary packages and defining three utility functions, write()
to write generated code to a buffer, check()
to do error handling, and freshTemp()
to generate unique variable names for temporary variables we create on the fly.
我们将从导入所有必需的包并定义三个实用程序功能开始,包括 write()
将生成的代码写入缓冲区, check()
执行错误处理,以及freshTemp()
为我们创建的临时变量生成唯一的变量名在飞行中。
Note: It’s generally bad practice to use panic()
for normal error handling in Go, but I’m tired of writing if statements
.
注意:在Go中使用panic()
进行常规错误处理通常是一种不好的做法,但是我厌倦了编写if statements
。
Similar to the checker, our generator has a core dispatch function that accepts a Node
and calls the corresponding gen function.
与检查器类似,我们的生成器具有一个核心调度函数,该函数接受Node
并调用相应的gen函数。
Let’s generate some Statements
. genProgram()
generates necessary headers and main()
function.
让我们生成一些Statements
。 genProgram()
生成必要的标头和main()
函数。
Generating Expressions
will look very similar to the code above. The main difference is that a temp
variable is returned representing that expression. This helps us handle more complex Expression
nesting.
生成Expressions
看起来与上面的代码非常相似。 主要区别在于,返回了一个代表该表达式的temp
变量。 这有助于我们处理更复杂的Expression
嵌套。
The final piece of code will be our C++ Builtin types. Without this nothing will work.
最后的代码将是我们的C ++ Builtin类型。 没有这个,一切都将无效。
测试代码生成器 (Test Code Generator)
汇集全部 (Bringing It All Together)
We’re now going to combine our lexer, parser, AST generator, type checker, and code generator into a final runnable program, main.go
.
现在,我们将词法分析器 , 解析器 , AST生成器 , 类型检查器和代码生成器组合到最终的可运行程序main.go
。
Note: I’m running this on a Windows so my C++ compiles into main.exe
. If this doesn’t work for you remove the .exe
extension.
注意:我正在Windows上运行它,因此我的C ++可以编译成main.exe
。 如果这对您不起作用,请删除.exe
扩展名。
To find some test programs to run go to github.com/Lebonesco/go-compiler/examples
.
要找到一些要运行的测试程序,请访问github.com/Lebonesco/go-compiler/examples
。
go run main.go ./example/function.bxhello Jeff3
And there you have it! We have completed a fully working compiler in Go!
在那里,您拥有了! 我们已经在Go中完成了一个可以正常工作的编译器!
Thank you for taking the time to read this article.
感谢您抽出宝贵的时间阅读本文。
If you found it helpful or interesting please let me know ???.
如果您觉得它有用或有趣,请告诉我???。
翻译自: https://www.freecodecamp.org/news/write-a-compiler-in-go-quick-guide-30d2f33ac6e0/
编写go语言用到的编译器