编译器由于涉及到编译原理,了解计算机科学的同学就能感触到,编译原理是较为抽象,无论从原理还是从实践上都是比较难把握的对象。在接触理论性较强,难度较大的问题时,最好的办法是从最简单的情况入手,先从感性上获得认知,为后面的理性认知打下基础,因此我们先从编译原理算法的基础入手,首先掌握词法解析。
上一节我们体会了“字节码”,现在问题在于”巧妇难为无米之炊“,我们总得有东西让我们产生字节码,这需要我们有能力将给定的代码进行解析后产生对应的字节码,而将代码转换为字节码的过程需要一系列非常复杂的操作,本节我们先从这些复杂操作的第一步,也就是词法解析开始。
词法解析简单来说就是对编程语言中的对象进行分类,例如在代码中,”1“,”234“,”3.14“等这类字符串我们将他们归类为NUMBER,用数值1来表示,类似”def", “map”, “string”,“with”,这类字符串我们将他们归类为KEYWORD,用数值2来表示,类似”+“,”-“,”*“,"/" ,"(",")",我们归类为OPERATOR,用数值3表示,类似"x",“y”,"my_str"等这类字符串归类为IDENTIFIER,用数值4表示,以此类推。如果我们把代码中对应的元素进相应归类后,一段看起来很复杂的代码其实就是一系列归类符号的组合,例如语句"x + (y - 1) "就可以转换成IDENTIFIER OPERATOR IDENTIFIER OPERATOR IDENTIFIER OPERAOTR NUMBR,由此词法解析其实是对源代码进行分析时所做的第一步抽象。
在词法解析中例如上面用来进行归类的标签,例如OPERATOR, IDENTIFIER,等我们统称为token,在python内核系列文章里面,我们下载了python编译器代码,里面有一个文件夹叫Grammar,在里面有一个文件叫token,打开之后能看到如下内容:
文件里面描述的就是对不同符号的归类,从上面可以看出左括号被归类为LPAR,所有的操作符号都有对应的归类,当读取一段Python代码后,将代码中不同符号根据上面的对应关系完成归类的过程就是词法解析。
接下来我们开始词法解析的实现,首先定义具体的数据结构,在上节基础上新建一个文件夹名为Token,在里面添加一个"token.go”文件,添加如下代码:
package token
type TokenType string
type Token struct {
Type TokenType //类型
Literal string //对应字符串
}
//例如数值”1“对应的实例为Token {"NUMBER", "1"}
根据python语法的token文件,我们先进行一系列常量定义:
const (
ILLEGAL = "ILLEGAL"
EOF = "EOF"
INDENT = "INDENT" //变量类型对应的归类
NUMBER = "NUMBER" //数值类型对应的归类
EQUAL = "=" //赋值操作符
PLUS = "+" //加号操作符
LPAR = "("
RPAR = ")"
LBRACE = "{"
RBRACE = "}"
COMMA = ","
DEF = "def" //关键字
)
上面我们堆一系列符号进行了归类,当然还有很多符号没有进行相应归类,后面我们用到的时候再处理,要不然会搞得过于复杂。接下来我们的目标是读取一段代码字符串,将字符串分割成不同的单元,然后将这些单元对应到给定分类。在相同目录下新建一个文件夹叫lexer,里面添加一个文件名为lexer_test.go,相应内容如下:
package lexer
import(
"testing"
"token"
)
func TestNextToken(t *testing.T) {
i