缩进词法分析

来源:互联网 发布:战龙三国许褚进阶数据 编辑:程序博客网 时间:2024/06/15 12:36
接下来会对larva实现的部分细节做一些分析,先看下编译器对于缩进词法的分析

用缩进来区分代码块或缩进参与词法、语法,python可能不是首创,不过大概是其中最有名的语言之一,相关的争议也一直不断,其实别说这个了,就连C语言中大括号是否应该另起一行都能争个几十年

正方认为,缩进可以让代码更美观,不过美观不美观也是一个主观看法,在这方面,反方提出,如果代码比较长,根据缩进区分代码块会造成可读性问题(虽然很多编辑器提供了缩进参考线),不过正方立即反驳,你干嘛把代码写那么长呢,超过50行的都单独写成函数不就行了?
另一个类似的争议点是,反方认为tab和空格难以区分,尤其是混用会造成各种阅读和调试上的混乱,有时候还会出现非常难以发现的bug,当然正方也会说,这是你们代码规范不够好,反方也会反驳说,一个语言对开发者要求过高了,本身就是一种缺陷

这个问题其实见仁见智,各有各的习惯,每个语言也有适合的场景,就编译器的实现来说,我也曾抛开习惯,考虑过两种方式的区别,其实区别主要还是在一些细节上,因为语法上都是命令式语言,只是词法格式不同罢了

从词法分析上看,引入缩进会增加难度,虽然实际也不是很难,但可能导致很难找到一个现成的词法工具如lex,python的词法分析就是自己写了个巨大的.c文件,在文档中也写了详细的和缩进相关的词法分析的过程

缩进的计算前面讲过,对于每个空格,算一个缩进,对于一个tab,则向前到下一个被8整除的位置,即当前缩进k=k+(8-k%8),换句话说就是tab算8个字符,这个在win的cmd、unix终端下大都是统一的,虽然一般的代码规范建议是4个空格。缩进的定义则是指每个逻辑行前面的前导空白

逻辑行是指语法上拥有完整逻辑意义的一行,如果代码没有折行,则每个物理行(即文本的每一行)就对应一个逻辑行,逻辑行和语句还是有点区别,比如:
if a:
这行不是语句,因为缺少if的代码段,但它是一个逻辑行

因此,对缩进的词法分析就可以这样:
1 解析缩进
2 解析一个物理行
3 若不存在折行则记录解析到的逻辑行,跳到2
4 忽略前导空白,若文件没结束则跳到2继续解析

判断是否折行,就需要根据具体的语法规定,一般来说折行有几种情况:

1 逻辑行以特定token结束,例如C语言中用“;”结束语句,则可一直读到分号,不过这里我们是假定一个语法用缩进表示代码块,同时用“;”结束语句,这样在分号之前可任意折行,实际中没碰到过这种语法。另外,像python中的if、while等,以冒号结束逻辑行,但是,python语法规定冒号不能另起一行,也不存在折行的问题

2 物理行以“\”结尾表示折行,在C和python都支持这么做,不过我不太喜欢这种方式
首先是字符串折行的问题,如果有一个很长的字符串,则可折行显示,比如:
"hello \world"
会被连接,且world之前的缩进(如果有)也会被算在字符串里面,如果这个字符串本身缩进比较深,代码会比较难看,python的三引号多行字符串也有这个问题,其实这个问题可以用多字符串并列的语法来解决:
"hello ""world"
多个字符串字面量并列,编译器会自动将其合并
另一个问题是,“\”后面必须紧跟回车,不能有后缀空格,否则不作为一个有效的折行,看上去这只是一个规定而已,但实际实现中,可能会被编译器利用,而导致一些看上去比较奇怪的结果,例如,由于代码中所有“\”加回车都肯定是折行,所以vs2008自己的编译过程(实际是cl.exe)就偷懒了,在词法分析之前就先做字符串处理,于是下面的代码:
"hello \\world"
会被转成"hello \world",继而报一个warning,因为\w不是合法的转义。而python则把这个折行处理放到词法分析部分,因此上面的\\会被转义为\,继而报一个缺少字符串引号的错

3 用未匹配括号来折行,即分析完一个物理行后,若存在未匹配的括号,则认为有折行,larva目前只采用这种方式,这种方式要求在词法解析时判断括号匹配,即糅合了部分语法分析时的内容,当然括号匹配本身也没啥难度。不过,这个做法如果要在词法分析阶段完成,则要求括号本身的语义只能作为括号,例如:
m = new map<int, vector<string>>()
如果语法支持泛型,则按照流行的方式用尖括号,但是,尖括号本身也是大于小于号,必须进行语法分析才能知道它的确切含义,而且这个代码两个反尖括号在词法分析阶段也只能解释为一个右移运算符,只有在语法分析阶段才能将其展开
括号折行的另一个问题是,对一个长的逻辑行折行时,可能需要增加额外的括号,但如果不小心,可能会和已有的语法冲突而导致非预期的结果,例如python中:
assert a == 0, "error"
如果对其添加括号折行:
assert (a == 0,        "error")
就改变了语义,因为python将(a==0,"error")看做一个元组,则这个assert永远为真,这时候逗号就不是assert的语法的一部分,而是构造元组的运算符了。正确的做法是:
assert a == 0, ("error")

顺便说下,上述过程没有考虑注释和空行,一般来说空行和单独出现的注释(即独占若干物理行)都可以直接忽略,对于详细的情况,在larva文档的词法元素一节做了更详细的解释(github上的doc目录)

以上只是说明了如何计算和解析缩进,每个缩进是一个独立token,在实际的编译过程中,还需要检查缩进的正确性,例如不能出现非预期的缩进,子代码块缩进需要比上层缩进大等,这个检查python是在词法分析阶段进行,同时将所有缩进token抹除,替换为块开始和块结束,简单说就是给缩进形式的代码显式添加类似C的{}token;larva采用另一种方法,没有抹除缩进token,而是在语法分析阶段做判断和处理

再比较缩进和花括号两种代码块分割方式,个人的一个直观感觉是,缩进代码块的定界“看起来”更弱一些,比如:
if cond:    i = 0else:    i = 1print i
这个代码看起来比较“自然”,如果改成:
if (cond){    i = 0;}else{    i = 1;}print i;
对这个代码,我的第一感觉是,if和else的代码块,以及最外层代码块是三个不同的作用域,因此如果if前面没有给i赋初值的话,最后的print感觉有些别扭,这也是larva被设计为缩进语法的原因之一,个人的主观感觉。而如果是静态类型语言,则可根据定义的位置严格区分作用域


虽然缩进语法对于改良程序员代码风格和习惯有好处,不过并不是说它在所有场景都拥有很美观的代码,比方说匿名函数(多行的lanmda这种)直接作为常量嵌入到代码中时,其格式可能需要随着出现位置而有更合适的风格,这时候用缩进可能要求太严格了,例如html里面可以嵌入js代码,如果改为嵌入python代码,在整体风格上就不太统一,这也是很多反对缩进语法的人的理由之一
0 0
原创粉丝点击