如何在yacc /野牛中实施#define
?
例如:
#define f(x) x*x
如果f(x)出现在任何函数中,则将其替换为宏替换参数“ x”。
例如,将f(3)替换为3 * 3。该宏也可以调用另一个宏。
通常不可能在解析器中进行宏扩展,至少不能在C样式宏中进行,因为C样式宏扩展不遵守语法。例如
#define IF if(
#define THEN )
是合法的(尽管恕我直言,这是非常糟糕的风格)。但是对于要在语法内部处理的问题,有必要允许宏标识符出现在输入中的任何位置,而不仅仅是可能在期望的位置出现。对语法的必要修改将使它的可读性大大降低,并且很可能引入解析器操作冲突。 [注1]
或者,您可以在词法分析器中进行宏扩展。词法分析器不是解析器,但是解析C风格的宏调用不需要太多的技巧,并且如果不允许使用宏参数,它将更加简单。这就是Flex在正则表达式中处理宏替换的方式。 (例如[{identifier}
。[注2]由于Flex宏只是原始字符序列,而不是像C样式宏一样的令牌列表,因此可以通过将替换文本推回输入流中来处理它们。(F)lex为此提供了unput
特殊操作unput
将一个字符推回输入流,因此,如果要推送整个宏替换,则必须一次将其unput
推回一个字符因此最后一个字符unput
是此后要读取的第一个字符。
这是可行的,但是很丑。而且它甚至不能扩展到C预处理器提供的小功能列表。而且它违反了软件设计的基本原理,即每个组件都只能做一件事(这样它才能做得很好)。
这样就剩下了最常见的方法,即添加一个单独的宏处理器组件,以便将解析分为词法扫描/宏扩展/语法分析,而不是将解析分为词法扫描/语法分析。 [注3]
在词法分析器和句法分析器之间工作的C风格宏处理器本身可以用Bison编写。正如我上面提到的,解析需求通常是最小的,但是仍然需要解析,并且Bison大概已经是项目的一部分。尽管我不知道执行此操作的任何宏处理器(除了我自己编写的概念验证程序),但我认为这是一个非常灵活的解决方案。尤其是,Bison句法分析阶段可以通过推式解析器实现,从而避免了产生整个宏扩展令牌流以使其可用于传统拉式解析器的需求。
不过,这不是设计宏的唯一方法。确实,它有很多缺点,因为宏扩展不是语法上的,既不考虑语法也不考虑范围。可能曾经使用C宏的任何人一次或多次被这些问题所咬;最简单的表现形式是定义一个宏,例如:unput
然后写
#define NEXT(a) a + 1
这不会产生预期的结果(除非预期的是违反了最后一条语句的句法形式)。此外,由于意外的名称冲突,任何需要使用局部变量的宏扩展都会迟早产生错误的扩展。卫生宏扩展试图通过将宏扩展视为对语法树(而不是令牌流)的操作来解决这些问题,从而使解析范式成为词法扫描/语法分析/宏扩展(解析树)。对于该操作,合适的工具可能是某种树解析器。
definitions