技术文章:编译器的词法分析

词法分析的代码就是个大型的if / else语句,不同的起始字符表示不同类型的词汇,根据起始字符选择对应的if / else分支就可以了。

在每个不同的if / else分支里,就是这类词汇的处理函数

例如0x开始的是16进制0开始的是8进制0b开始是2进制1-9开始的是10进制,那么就可以这么写:

FILE* fp = fopen("source.c", "r"); // 打开源码文件

char c0 = fgetc(fp); // 读取第1个字符,也就是起始符

if ('0' == c0) { // 数字分支,16进制、8进制、2进制,或0.开头的浮点数

char c1 = fgetc(fp); // 读取第2个字符

if ('x' == c1)

return number_base16();

else if ('b' == c1)

return number_base2();

else if ('.' == c1) // 浮点数,一律按double类型处理

return number_double();

return number_base8();

} else if ('1' <= c0 && '9' >= c0) // 10进制,可能是整数,也可能是浮点数

return number_base10(); // 如果发现小数点的话,在这个函数里处理

else if ('.' == c0) // 小数点开头的浮点数

return number_double();

如果要是支持以小数点开头浮点数(前导0省略),加一行else if就行,如上。

词法分析的if / else分支,都是根据起始字符来划分的,处理函数的结束都是根据终止字符来的。

当进行16进制的分析时,后续的字符必须都是0-9或a-f或A-F(也可以是UL之类的后缀),除了这些之外的其他字符都是终止字符

发现终止符的时候,因为终止符并不包含在当前数字内,所以要把它放回输入缓冲区

这种情况在词法分析时是很常见的,不光是分析数字,也可以是分析运算符、标志符

所以把输入缓冲区做成一个双向链表是有必要的(也可以用其他数据结构)。

总之,在从源码文件里读字符到缓冲区时,输入缓冲区是个先进先出(FIFO)的队列。

词法分析器从缓冲区读取字符,或者把字符放回到缓冲区时,输入缓冲区是个

放回字符,一定是把字符压回栈顶(push)。

读取字符,一定是把字符从栈顶pop出栈(pop)。

我在scf里使用的双向链表,双向链表处理取出和放回时比较无脑[捂脸]

16进制整数的词法分析

上图就是scf_lex.c里的一个片段,16进制整数的分析,但我都没支持后缀的ULL。

标志符的词法分析是以下划线_或字母a-z和A-Z开头的,之后可以包含数字。

char c0 = fgetc(fp);

if ('_' == c0 || ('a' <= c0 && 'z' >= c0) || ('A' <= c0 && 'Z' >= c0))

return lex_identity(); // 标志符的分析

关键字包含在标志符里的,它们都是一样的词法规则的字符序列,无非就是关键字被编译器保留做特殊用途,不能当作普通的标志符。

在词法阶段,是没法确定标志符到底是类型、变量、常量、还是函数的,只能确定它是不是关键字

scf的标志符的词法分析

NULL,__func__, __LINE__ 都在这里处理好了,如上图。

从数学上来说,这类字符串处理程序都是一个有限的自动机

它根据读入的字符变化来更新自己的状态,把字符读一遍就完成了词法分析。

词法分析的状态机

运算符的分析也是类似的,+和+=是一个分支,*和*=是一个分支,etc。

*=肯定是乘等,但*并不能确定是乘法还是指针,所以只能叫它星号(star)。

符号/的分析稍微复杂点,因为//和/**/的注释也是以/开头的,如下图。

符号/的状态机

词法分析并不难写,只需要一点耐心就可以写出来。

它是编译器里最简单的模块,有兴趣的可以参考我的scf_lex.c文件。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章