构造AST在compiler中是否是一个必不可少的步骤? 由于attribute grammar和one-pass compiler或者说语法制导翻译(syntax-directed translation,SDT)联系很紧密,所以attribute grammar和这个问题有存在间接的的关系。
答案是否,构造AST并不是一个必要的步骤(可以思考构造AST的目的是什么),甚至在计算机资源极其有限的几十年前,one-pass compiler还是一个比较普遍的做法。
什么是Attribute Grammars
Attribute Grammars were invented by Donald Knuth as a way to unify all of the stages of compiling into one. They give a formal way to pass semantic information (types, values, etc.) around a parse tree.
注:再次跪拜Don Knuth大神
首先attribute grammars被提出来用于将多个编译步骤整合成一个,例如你要编译数学表达式,此时就没有必要生成AST并为其生成binary了。“attribute grammar + 语法制导”就直接将该表达式的值evaluate出来了。针对应用场景使用合适的方法才是王道,否则就可能像见识少的我一样,把特例当做通用解法使用。
Attribute Grammar为何能够做到将不同的编译阶段整合成一个呢?Attribute Grammar就是在Grammar上扩充了Attriute的文法,核心就在于这些Attribute。Attribute可以是类型,可以中间代码,也可以是表达式的值。我们就可以根据这些attributed的文法,将类型检查,中间代码生成以及表达式求值融合到parser中。甚至于一些constant folding,available expressioin也可以融合进去。
Attribute Grammar的核心在于这些Attribute,而关于Attribute如何定义以及如何evaluate则视问题不同而不同。
下面我摘PLP中的一个AG的示例,简单的复述一下整个过程。
上面的文法用于生成一个数学运算式,我们可以在其上附着一些attribute,如下所示:
Attribute:
Attribute:
Attribute:
Attribute:
Attribute:
Attribute:
Attribute:
Attribute:
Attribute:
使用开头的可以称为Semantic Functions(与PLP稍有不同),针对上述AG,这些semantic functions只是简单的数学运算,它们的参数都来源于当前文法内部。
至此我们就可以知道Attriubte Grammar的作用是将***文法和一些有意义的sematic function粘和起来***。这些semantic function都是人为赋予的,我们同样可以在代码生成阶段,将sematic function替换为一段段machine code。
Evaluate Attributes
PLP将evaluate attribute称作Parse Tree的annotation(或decoration),一个简单的示例如下图所示:
注:图片摘自PLP 4.3
从上图中我们可以看到整个evaluate的过程就是后序递归下降,最终我们得到结果8,这个Parse Tree不是必须的,并且运算符号的优先级已经在parse的时候隐含的处理了。
Synthesized Attributes
对于数学运算式对应的Attribute Grammar,我们可以称之为Synthesized Attributes。
Syntathesized Attributes: their values are calculated (synthesized) only in productions in which their symbol appears on the left-hand side. This means that the attribute flow - the pattern in which information moves from node to node - is entirely bottom-up.
Synthesized Attributes又称之为S-attribute,龙书翻译成综合属性和S属性,注意终结符的属性值来源于scanner。
Inherited Attributes
与Synthesized Attributes对应的另外一种属性是Inherited Attributes,又称之为L属性(L意思是Left-To-Right)。
In general, we can imagine attributes whose values are calculated when their symbol is on the right-hand side of the current production. Such attributes are said to be inherited.
Inherited Attributes能够将context information从上至下,从左至右进行传递,一个比较典型的Inherited Attributes应用是Symbol Table和Type check。
下面给出一个既含有S属性又包含L属性的例子,这个例子主要用于automatic type evaluation,表示symbol table。
Attribute:
Attribute:
Attribute:
Attribute:
Attribute:
Attribute:
Attribute:
其中属性是S属性,而属性是L属性。对于下面的两行代码,对应的decorated tree如下所示。
int a, b, c;
string s;
从上图中我们可以清楚的看到,L属性沿着和传递,S属性沿着的方式传递。
在parse的时候这里有一个准则,
When we get to a node during parsing,
- we must have all of the information we need to evaluate its inherited attributes.
- Before we leave the node we must have all of the information we need to evaluate its synthesized attributes.
读书的重点在于思考和理解,否则无疑是向脑子里倾倒垃圾。