我们必须要做的第一件事就是定义词法分析器输出的Tokens。我们将从几个标记定义开始,然后在拓展词法分析器时添加更多。
我们将在第一部中的词法的Monkey语言的子集如下所示:
let five = 5;
let ten = 10;
let add = fn(x,y) {
x + y;
};
let result = add(five,ten);
让我们分析一下:这个例子中包含哪些类型的Tokens?首先,有像5和10这样的数字。这些是很明显的。然后我们有变量名称x,y,add和result。然后还有语言的这些部分不是数字,只是单词,但也没有变量名,像let和fn。当然,也有很多特殊字符:(,),{,}=,,,,;。数字只是整数,我们将这一对待它们并给他们一个单独的类型。在词法分析器或者解析器中,我们不关心数字是5还是10,我们只想知道它是否是数字。"变量名"也是如此:我们称它们为“标识符”,并且一视同仁。现在,换句话说,那些看起来像标识符但实际上不是标识符的词,因为它们是语言的一部分,被称为“关键字”。我们不会将这些组合在一起,因为无论我们是否遇到aletor afn,它们都会在解析阶段产生影响。
我们确定的最后一个类别也是如此:特殊字符。我们将分别对待它们中的每一个,因为源代码中是否有a(或者a)是一个很大的区别。让我们定义我们的Token数据结构。它需要哪些领域?正如我们刚刚看到的,我们肯定需要一个“类型”属性,例如,我们可以区分“整数”和“右括号”。并且它需要一个字段来保存Token的字面值,这样我们之后可以重用它并且"number"Tokens是a5或者a10的信息不会丢失。在一个newtoken包中,我们定义我们的Token类型和我们的TokenType类型。
// token/token.go
package token
type TokenType string
type Token struct {
Type TokenTyoe
Literal string
}
我们定义TokenType类型为string。这允许我们使用多种不同的值作为Token类型,这反过来又允许我们区分不同类型的Tokens。使用字符串还有一个优点是易于调试,无需大量样板和辅助函数:我们可以只打印字符串。当然,使用一个字符串可能不会带来和使用一个int
或者一个byte
一样的性能,但对于本书而言,string是完美的。
正如我们刚刚看到的,Monkey语言中的不同Token类型数量有限。这意味着我们将可能定义TokenTypes为常量。在同一个文件内,我们添加这些内容:
const(
ILLEGAL ="ILLEGAL"
EOF="EOF"
// Identifiers + literals
IDENT ="IDENT"// add, foobar, x, y,...
INT ="INT"// 1343456
// Operators
ASSIGN ="="
PLUS="+"
// Delimiters
COMMA=","
SEMICOLON =";"
LPAREN ="("
RPAREN =")"
LBRACE ="{"
RBRACE ="}"
// Keywords
FUNCTION ="FUNCTION"
LET="LET"
)
正如你所看到的两种特殊类型:ILLEGAL和EOF。我们在之前的例子中没有看到它们,但是我们仍然需要它们。ILLEGAL表示我们不知道的标记/字符,而EOF代表"文件结束"(end of file),它告诉我们的解析器稍后它可以停止了。到目前为止一切顺理。我们准备开始编写我们的词法分析器(lexer)。
< 1.1词法分析 | > 1.3词法分析器 |
---|