我读过yacc为LALR(1)语法生成了自底向上的解析器。我有一个Java 1语法,可用于生成三个地址代码,严格来说是LALR(1),但是我采用的翻译方案使其具有L属性。现在,我了解到在自下而上的解析过程中无法翻译L归因的LR语法。那么,可以在这里使用yacc吗?如果是的话,yacc如何解决这个问题?
最佳答案
除非您提出具体,详细的问题,否则您将无法得到很好的答案。这是一个模糊的方法草图。
对于自下而上的解析器来说,合成属性显然不是问题,因为它们是在相应终端的最终归约操作中计算的。因此,问题归结为“自下而上的解析器如何计算继承的属性?”
由于语法是L归因的,因此我们知道,任何继承的属性都是根据其左兄弟姐妹的属性计算得出的。 Yacc /野牛允许将动作插入到右侧的任何位置,并且这些"Mid-Rule Actions" (MRAs)
被遇到时执行。 MRA可以精确地使用它的左兄弟姐妹,因此这是计算继承属性所需要的全部。
但是,这并没有显示该属性实际上是如何被继承的。在某些规则中,在语法符号前插入的MRA当然可以用于部分计算该符号的继承属性,但是继承属性也可以使用子代的综合属性。
为此,我们需要做两件事:
在非终端之前插入MRA,该MRA会收集左兄弟属性。由于MRA也是语法符号,因此该MRA将是最后一个左兄弟姐妹,实际上是终端孩子的最小叔叔。 (您不一定需要MRA;您可以插入“标记”:一个非终端,其唯一的产生为空,并且其动作是MRA主体。但这并不是那么方便,因为动作必须要达到语义。前面的语法符号的值。或者您可以将产生的内容分成两部分,以便两个动作都是最终的。)
在终端的还原操作中访问叔叔的属性。
Bison / yacc允许第二步,方法是让您使用一个非positibd符号索引来引用解析器堆栈中的插槽。特别地,$0
指的是紧接在父产品中非终结符之前的符号(我在上面将其称为叔叔)。当然,要使其正常工作,您必须确保在出现非终端的每个产品中,叔叔是相同的非终端(或至少具有相同的语义类型)。这可能需要添加一些标记。
说到语义值,您也许可以使自己满意,即给定非终结符的所有叔叔都是相同的,或者至少具有相同的类型。但是bison不会进行此分析,因此如果您弄错了它就不会警告您。小心!另一个后果是,您必须告诉野牛类型是什么,所以不能只写$0
:您需要$<tag>0
。
注意:
并非总是可能在L归因的LR语法中处理继承的属性,因为在遇到非终结符的时刻,解析器可能还不知道非终结符实际上将构成解析树的一部分。 。在LL语法中不会发生此问题,因为在LL解析中,解析器只能预测一个非终结符,如果其余输入有效,则可以保证解析中存在非终结符。
可以自下而上地解析任何LL语法,因此L归因的LL语法没有问题。但是自下而上的解析器可以做得更好。它不需要完整的语法为LL。仅那些将被分配继承属性的非终端的决策点才需要是LL确定的。
通过将MRA或标记放置在非末端之前的技术来强制执行此限制。换句话说,在LR语法的某些点添加标记(或MRA)可能会使LR属性无效。 bison manual中对此问题进行了很好的讨论,因此,除了观察一个细节之外,我在这里不做详细说明。
上面概述的用于传播继承属性的技术在战略要点使用MRA(或标记)来保存继承属性。为了进行解析,必须减少这些产生,因此,如野牛手册的上述部分所述,可能有必要重新排列语法以消除冲突。在极少数情况下,这种重写甚至是不可能的。
但是,消除冲突可能仍会导致在某些非终结点需要该值的情况下传播继承的属性的语法,而不能保证最终会减少非终结点。在这种情况下,将不必要地计算继承的属性,然后将其忽略。但这不应该是一个问题。属性概念的本质是属性是功能性的。换句话说,该计算没有副作用。
没有副作用意味着属性语法解析器应该可以按照尊重属性依赖性的任何顺序自由评估属性。尤其是,这意味着您可以通过将属性计算转换为延续来轻松实现对属性的正确评估,这种技术有时被称为“惰性评估”或“ thunking”。
但是,总是存在精确地使用MRA来产生副作用的诱惑。一种非常常见的副作用是将三地址代码打印到输出流。另一个是变异持久数据结构,例如符号表。这不再是L属性分析,因此此处提供的建议可能不适用于此类应用程序。