编译器中的抽象语法树:如何准确地表示函数?

时间:2014-08-30 13:59:53

标签: c regex bison abstract-syntax-tree

我们正在创建一种非常简单的编程语言,使用Flex和Bison进行解析和语法分析,并使用C来构建编译器。 在直接进行汇编之前,我们将根据语言规则创建一个抽象语法树。但是我们在用语言代表一个特定的函数时遇到了麻烦。 该功能描述如下:

  

FILTERC:它将条件和表达式列表作为输入,并返回这些表达式中有多少与条件匹配。它可以是单个或复合条件。   它以这种形式使用: FILTERC(条件,[表达式列表])   条件必须在每个元素之前有一个下划线,表示表达式应放在哪里进行比较。示例: FILTERC(_> 4和_< = 6.5,[a + c,b,c-a,d])

这就是“过滤器”功能在BNF规则中表达的方式(我们实际上使用了Flex的标记,但我用实际字符简化了它,因为那不是重点,语法分析是由Bison正确完成的):

filter ::= FILTERC ( condition_filter , [ expression_list ] )
;
condition_filter ::= comparison_filter | comparison_filter AND comparison_filter | comparison_filter OR comparison_filter
;
comparison_filter ::= _ > expression | _ < expression | _ == expression | _ >= expression | _ <= expression | _ != expression
;
expression_list ::= expression | expression , expression_list
;
expression: term | expression + term | expression - term
;
term: factor | term * factor | term / factor 
;
factor: ID | INT_LITERAL | REAL_LITERAL | STRING_LITERAL | ( expression ) | filter
;

我们现在必须编写创建抽象语法树节点的函数。在低级别,“filterc”函数只是一堆“IF”来验证每个表达式是否与条件匹配,只是现在表达式将被放置在下划线所在的位置。所以它会是这样的:(表达式)(比较运算符)(条件)

问题是,实际的FILTERC句子是“向后”读取的:首先读取表达式然后将其与条件进行比较。但是顺序读取程序:在找到实际表达式之前读取下划线。所以我们真的很困惑如何构建树。

我不打算添加我们用来创建树的节点和叶子的所有代码,否则这将是一团糟。但基本上,有一个函数创建具有2个子节点(左和右)的节点,当不应该有任何子节点时,这些指针设置为null。我们使用的基本结构是将操作符放在根节点中,将操作数作为子节点放置(例如:在“if”句子中,“if”关键字应该是根,条件是左子节点和代码阻止执行,如果true将是正确的孩子)。像这样:

IF condition THEN block {thenPtr = blockPtr;} ENDIF {createNode("if", conditionPtr, thenPtr);}

(“条件”和“块”在其他地方定义,其指针是在其中创建的。)

我们能够为表达式正则表达式和语言中的所有其他规则成功创建树,但这个“过滤器”功能确实令人困惑。

1 个答案:

答案 0 :(得分:2)

确实,当解析器读取表达式的一部分(例如,“&gt;”)时,它还没有足够为表达式构建树。对于您的语言中的任何概念(“非终结”)也是如此。从这个角度来看,我看到你可能会感到困惑。

显然你不明白像Bison这样的LR解析器是如何工作的。假设我们有规则R1,R2,......规则有右手边,例如Rn = T1 T2 T3;每条规则的右手边长度为L(Rn)。

您需要的关键思想是,LR解析器从输入流中从左到右收集(“堆栈”,是的,它确实使用了一堆令牌)令牌。这些步骤称为“轮班”。解析器重复移位,不断地寻找指示已经读取了足够的令牌(例如,T1,T2,然后T3)以满足某些语法规则Rn的右手侧的情况。解析器生成器及其生成的LR表的神奇之处在于,解析器可以立即有效地跟踪所有“实时”规则,我们不打算在此进一步讨论。

在该点已经识别出右侧的点处,LR解析器执行“减少”动作并用非终结令牌Rn替换与规则主体匹配的堆叠令牌(“弹出”然后堆叠L(Rn)次并按下Rn“)。在返回从输入流收集终端令牌之前,它会尽可能多地进行减少。 用一个非常小的语法手工模拟这个过程是值得的。 [一个细微的细节:一些规则有空的右手边,例如L(Rn)== 0);在这种情况下,当一个减少发生弹出时,是的,这听起来很有趣,但它是致命的正确]。

在解析器执行reduce操作的每一点上,它为解析器程序员提供了执行其他工作的机会。这项额外的工作几乎总是“树木建筑”。显然,组成规则Rn的令牌都已被看到,因此如果令牌都是终端,则可以构建表示Rn的树。实际上,如果已经看到Rn的所有标记,并且Rn包含一些非终结符,则必须有减少动作来生成每个非终结符。如果他们中的每一个都生成了一个表示自己的树,那么当包含非终结符的规则减少时,已经为其他非终结符号生成了树,并且这些树可以组合以生成当前树规则。

像Bison这样的LR解析器生成器工具可以帮助您,通常通过提供可以在reduce-action中调用的树构建操作符。它还有助于使已处理的非终结树的树可用于reduce-action,因此它可以将它们组合在一起以生成reduce动作的树。 (它是通过跟踪与令牌堆栈并行的堆栈中生成的树来实现的。)在任何时候它都没有尝试减少,或者你是否尝试过生成一棵树,你没有所需的所有子树

我认为您需要手动仔细阅读Bison ,当您尝试实施解析器和缩减时,所有这些都将变得清晰;手册有很好的例子。很明显你没有这样做(害怕不知道如何处理树木?),因为a)你所表达的规则被打破了;没有办法生成一个术语,b)你没有任何嵌入式减少动作。