这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战
本文为译文,原文链接:dinosaur.compilertools.net/yacc/
接上文,继续《9: Hints for Preparing Specifications》。
左递归
yacc解析器使用的算法鼓励这个叫做"左递归"语法规则:下面这种格式的规则
name : name rest_of_rule ;
这些规则频繁出现在写序列和列表的规范:
list : item
| list ',' item
;
and
seq : item
| seq item
;
在这些案例的每一个,第一个规则将会只被reduce第一个item,第二个规则将会被reduce第二个和所有随后的项目。
至于右循环规则,比如说:
seq : item
| item seq
;
解析器将会大一些,items会被看到,并且reduce,从右到左。更严重的是,在解析器中的内部栈将会有溢出的风险,如果有一个非常长的序列被读取的话。因此,用户应该使用坐递归无论何种原因。
值得考虑一下是否一个序列带有0个元素有什么意义,如果真的有,考虑写这个序列规范带有一个空的规则:
seq : /* empty */
| seq item
;
再一次,第一个规则将会一直被只reduce一次,在第一个项目被读取之前,然后第二个规则将在每个item读取的时候被reduce一次。允许空序列经常带来更高的普遍性。然而,如果yacc被要求去决定已经看见哪个空序列可能会出现冲突,这时候它还没有看到足够的信息。
词法改编(Lexical Tie-ins)
有一些词法决策依赖上下文。例如,词法分析器可能想正常删除空格,但不是那种引号括起来的字符串。或者名称可以在声明中输入到符号表中,但是不是在表达式中。
处理这种情况的一种方式是创建一个全局的标记,这个标记能被词法解析器校验,并且通过actions动作设置。例如,假设一个程序由0或者更多声明组成,接着是0或者更多声明。考虑一下:
%{
int dflag;
%}
... other declarations ...
%%
prog : decls stats
;
decls : /* empty */
{ dflag = 1; }
| decls declaration
;
stats : /* empty */
{ dflag = 0; }
| stats statement
;
... other rules ...
dflag标识现在读取到statements时是0,读取到declarations时是1,除了第一个statement的第一个token。这个token必须在它能说明声明模块已经结束并且statements已经开始之前被解析器看见。在很多案例中,这个单一的token无法影响到词法扫描。
这种“后门”的处理可以静心涉及到有害的程度。然而,它代表了一种处理很难的事情的方法,即如果有可能就去做别的事情。
保留字(Reserved Words)
有些编程语言允许用户使用类似"if"的单词,这种通常是保留的,作为标签或者变量名称,前提是这样的是用哪个不与这些名称在编程语言中的合法使用相冲突。在yacc框架中这是极其难以完成的;很难传递信息给词法分析器告诉它:这个"if"的实例是一个关键字,那个实例是一个变量。用户可以尝试一下,使用最后一个子章节的机制,但是很难。
很多方法来让这种实现更简单都会考虑周全。直到那时候,最好关键字得到保留;那就是禁止当做变量名称使用。无论如何,有很多格式上的原因来偏向这种。
下一节再为大家带来《yacc的高级主题》,敬请期待。