原文地址:苹果梨的博客html
以前为了快速进入主题,咱们约定了表达式里只会出现个位数的数字。如今是时候打破这个规则,支持多位数的数字了。为了支持这点,咱们就须要接触一个新的步骤——词法分析。git
词法分析就是把一个完整的语句拆分红一个个词(token),方便以后进行进一步的语法分析。github
举个简单的例子:今天真热
,将会被拆分红<今天>, <真>, <热>
。固然拆分红<今>, <天真>, <热>
也是一种可能,可是这样的分词方式不符合汉语的语法。正则表达式
好在计算机语言大部分是英文的,词与词之间通常用空白符隔开,很容易拆分。举个代码的例子:a = 1.1 + 2
将被拆分为<id: a>, <等号>, <浮点数: 1.1>, <加号>, <整数: 2>
,固然咱们也能够把加号和等号都算做运算符,作必定的聚合获得<id: a>, <运算符: =>, <浮点数: 1.1>, <运算符: +>, <整数: 2>
。json
有人要问为何拆分token的逻辑要作成单独的词法分析步骤,而不是放在语法分析里一块儿作?这是个好问题,还真的有点难回答。从我我的的观点来讲主要的两点多是:函数
要问得更具体的话,仍是建议各位在本身写编译器的过程当中自行体会一下……😂性能
上面也提到了词法分析器主要是用来拆分token的,可是词法分析器还要负责一些别的工做。咱们总结下词法分析器的主要工做范围:ui
我准备一开始作的简单点,先把必备的前两条功能给实现了。spa
首先咱们得定义一个用来描述token的结构体:3d
typedef struct {
int type;
int value;
} slm_token;
复制代码
关于token的类型,前文也提到了,运算符能够作必定聚合,也能够每种运算符算一种类型。我这里就不作聚合了,把咱们前文出现过的token类型都定义出来:
enum {
SLM_EXPRESSION_TOKEN_UNKNOWN = 0,
SLM_EXPRESSION_TOKEN_DIGITS,
SLM_EXPRESSION_TOKEN_ADD,
...
SLM_EXPRESSION_TOKEN_CLOSE,
SLM_EXPRESSION_TOKEN_END
};
复制代码
而后扩展下咱们的slm_expr
结构体,之后语法分析器就不该该直接读expStr
而应该从token
里取值啦:
typedef struct {
const char *expStr;
slm_token token;
int errType;
} slm_expr;
复制代码
词法分析的核心函数通常叫作next
或者scan
,咱们这里就叫它next
吧。它主要实现的功能是读取下一个有效的token,存到slm_expr
结构体的token
成员里以供语法分析器使用。
C语言的词法分析十分简单,由于根据token的首字符就能区分出token的类型:若是首字符是数字那必定是个数值token;若是首字符是字母或下划线那必定是个id类的token,至于这个id是关键字仍是函数名、变量名那就另说了。怎么样?是否是忽然明白了大部分计算机语言里变量名不能以数字开头的缘由?
这一章里面咱们暂时还用不到id类的token,因此主要讲一下数值token的处理:
void next(slm_expr *e) {
...
if (isdigit(*e->expStr)) {
e->token.type = SLM_EXPRESSION_TOKEN_DIGITS;
e->token.value = *e->expStr - '0';
(e->expStr)++;
while (isdigit(*e->expStr)) {
e->token.value = e->token.value * 10 + (*e->expStr - '0');
(e->expStr)++;
}
}
...
}
复制代码
可见若是发现一个token是以数字开头的,那么咱们能够循环读取后面连续的数字,直接把整个token完整的数字值读取出来,供语法分析器在后面的分析中使用。
词法分析的过程通常能够用状态机来描述,上面的解析过程对应的状态机能够用这么个图来表示:
能够看出来这种图和流程图类似,更适合用条件分支及循环语句来实现它的逻辑。而后咱们能够大体的补全一下整个词法分析器的状态机图:
接下来照着状态机图来实现代码逻辑就行了,在这里不贴完整代码了。注意若是出现了用状态机没法描述的token,那么这必定是个非法的token。
用状态机图能够直观的表示词法分析的流程,之后扩展数值类型支持浮点数之类的,均可以从画状态机图开始。好比大部分计算机语言支持的数字类型,能够用下面的状态机图来表示(图片来自Online JSON Viewer):
除了状态机,另外一个超级适于描述词法分析器的就是正则表达式,有兴趣的同窗能够自行去了解下。著名的词法分析器Lex就是用正则表达式描述词法规则的。
以最典型的number
函数为例:
int number(slm_expr *e) {
int hasMinus = 0;
if (e->token.type == SLM_EXPRESSION_TOKEN_SUB_OR_MINUS) {
TRY(next(e));
hasMinus = 1;
}
if (e->token.type != SLM_EXPRESSION_TOKEN_DIGITS) {
THROW(SLM_EXPRESSION_ERROR_EXPECT_DIGIT);
}
int result = e->token.value;
TRY(next(e));
if (hasMinus) {
result *= -1;
}
return result;
}
复制代码
咱们把以前从e->expStr
直接取值的代码都换成读取e->token
。还要把(e->expStr)++
的地方都替换为TRY(next(e))
,加上TRY
是由于next
里面也会报非法token的错误。固然不能忘记的是,在main
函数里必须预先调用一次next
,否则首次进入语法解析器的时候e->token
会是空的。
把全部语法分析步骤里的代码替换完以后,咱们就能够获得一个能剔除空格、识别非法字符和多位数字的解析器啦。完整的代码我就不在这里全贴出来了,存放在SlimeExpressionC,欢迎你们自取。