词法分析

来源:互联网 发布:淘宝教学视频全集 编辑:程序博客网 时间:2024/05/05 08:07
一个高级语言程序在计算机中一般以文件形式存在,文件是一堆字节的集合,而它要表达的含义显然不是一堆字节,最小单位是一个个词,因此编译一个程序,一开始的工作就是词法分析 

龙书的词法分析部分,掺杂了很多自动机相关的东西,其实这些在计算理论有更详细的描述,在编译原理里面讲大概是希望能让零基础的人看懂,可惜这样一来内容就比较臃肿,而且好像也讲的不是很系统反而让人看糊涂,就好像算法导论里面讲NP一样,虽然没有错误但个人觉得有误人的嫌疑,NP的学习还是结合图灵机更好点 

词法分析是要用到自动机原理的,但并非说一定要直接用自动机(早期是需要的),计算理论里讲的很清楚,有限状态自动机和正则语言是等价的,因此就现在来说,犯不着自己写NFA,或用lex生成NFA,一个正则表达式引擎足够了,所以重点就在于语言本身的词法相关的设计了,不过一般来说大部分语言的词法都同质化的,规则差不多,出现在代码里面的词一般也就三种:常量,标识符,符号,一个正则搞定(python表示): 
_TOKEN_RE_EXPR = (     #float     r"""(\d+\.?\d*[eE][+-]?\w+|""" +     r"""\.\d+[eE][+-]?\w+|""" +     r"""\d+\.\w*|""" +     r"""\.\d\w*)|""" +     #symbol     r"""(!=|\*\*|==|<<|<=|>>|>=|\W)|""" +     #int     r"""(\d\w*)|""" +     #word     r"""([a-zA-Z_]\w*)""") 

比较复杂的就是浮点数的表示形式有很多种,其余都很简单,需要说明的两种数字都以\w+或\w*结尾,假如整数写\d\d*这种,像111aaa这种就会被拆成111和aaa,实际应该是个编译错误,再者整数可能也有0x12f,123UL这类表示形式,或其他自定义的末尾表示,如果每种情况都写在正则就很麻烦了,所以统一拿一个\d\w*然后再详细分析,同样的道理,单字符符号统一用\W,可能匹配到一些不合词法的字符,比如$或非ascii码,拿出来再分析 

词法分析中需要注意的一点是最长子串原则,简单说就是会匹配尽可能长的一个合法词,比如碰到==,会匹配为相等逻辑操作,而不是两个赋值符号,当然这种时候两个赋值连在一起是没有什么意义的,但有时候就会出现歧义,比如C语言中: 
a=b/*c; 
写这个代码的人原意大概是用*c去除b,结果赋值给a,但根据最长子串原则,/*开启了一个C语言的注释,如果编辑器没有颜色和高亮显示,可能比较难看出来,当然如果按代码规范加入适当空格就规避这个问题了 

有时候语法的发展会和这个原则冲突,比如C++的模板如果嵌套定义,后面连续两个反尖括号>>会被解析成右移符号,因此中间要加一个空格,这样不太美观,所以后面的编译器和C++标准注意到了这个问题,做了特殊处理,但歧义仍然可能存在,比如: 
A<B<10 >> 1>> 
将10>>1的结果作为模板参数,其实就算没有右移符号这个问题,也存在上面的情况,因为>大于运算本身也是表达式,好在这种情况可以通过加个()来规避 

词法分析光靠一个正则还不够,还需要检查每个词的合法性,比如上面的111aaa。另外,正则处理一些复杂的词很麻烦,比如说字符串,无论多长都是一个常量词,上面的正则只能分析出一个起始的引号,拿到引号后可能需要自己写个循环来读完整个词,因为字符串格式往往比较复杂,有各种转义符,如果不合法还需检查具体的错误等。最终的输出是一个token_list,其中每个词需要有自己的类型(常量,标识符,符号),每个常量有自己的数据类型(字符串,整数,浮点等),每个符号转为内部表示,方便后面的语法分析 

上面这个正则没有考虑空格等空白符,空白是用代码处理了,其实加个\s*即可,这是假设了空白符在词法中仅作必要的分隔作用,如C的变量定义int a中的空白,但在有些语言中,空白符有更强的词法特性,比如python用缩进来标识代码块,后面也打算做成这种,所以需要说一下 

首先是缩进的计算规则,如果都用空格,或都用tab,一般没什么问题,如果两者混用,python规定统一折算为空格计算,折算方法是,如果碰到tab,则空格数量增加至当前位置向前的最小的一个8的倍数,和unix下的文件编辑的风格一致 

然后是按行解析的问题,C和java因为是以分号表示语句结束,所以换行一般不做处理,python的语句是以一行结束为结束,但是可以折行,编译器需要区分这两种情况,一般折行有三种情况:1,上一行以“\”结束;2,三引号多行字符串;3,上一行有未匹配完的括号 

做这种语言的词法分析时,可以先分成一行行字符串,每行前导空白符折算成缩进空格,然后在分析过程中对上述三种折行情况作状态处理即可,如果出现折行则恢复前导空白(多行字符串),或忽略,最后将缩进空格也一起放进token_list,就是有点麻烦 

当然,还有一些细节问题,比如编译如果出错,应该报出是哪一行哪个字符,分析过程中记录下这些信息就行了
1 0