自顶向下的语法分析:
递归下降法是自顶向下分析方法的通用形式。递归下降可能需要回溯。
而预测分析属于递归下降的一个特例,不需要回溯。预测分析程序试图利用一个或多个先行记号来预测出输入串中的下一个构造。LL(1)是一类最常用的预测分析程序,python的parser就是用LL(1)写的,LL(1)是自底向上算法的前奏。
自己创建一门语言,首先是要写出它的文法,然后是把文法转换成parse程序。个人感觉第一步写出文法的步骤比较重要(当然也可以从书上抄一些),转换程序是有固定的规则的,比较简单。
表达式(expression)和语句(statment)是两个概念:基本程序步骤由语句组成,而大多数语句都由表达式构成。
考虑如下Tiny的文法:
语法树可以采用树的孩子兄弟表示法,
一、递归下降法
用递归下降法写parser程序的过程,就是把图中的文法用程序翻译出来。每个文法都对应一个处理该文法的子例程。
比如 factor -> ( exp ) | number 这个文法规则,可以用如下伪代码来表示:
def factor():
switch(token):
case ( :
match( ( );
exp;
match( ) );
case number:
match(number);
else error;
end switch;
end
比较麻烦的是对左递归的处理,递归下降和LL(1)使用不同的方法来处理左递归的问题。
递归下降使用拓展巴克斯范式(EBNF)把左递归写成其他形式,
类似exp->exp addop term | term这样的表达式是左递归的,并不好直接用递归下降程序来翻译
所以通过改写成 exp->term { addop term } 可以方便表示(大括号( { } )内包含的为可重复0至无数次的项。)
exp -> exp + num|num #这里就是递归,我们在定义“exp”这个概念,而它的定义里面又用到了自己本身!
exp -> term { addop term }
#都是匹配形如 num (+ num) (+ num) ... 的串 。在数学上,这两个表达式是等价的。
对应的exp匹配程序如下:
def exp():
term();
while token = '+' or token = '-':
match(token);
term();
end while
end
程序运行到term()的时候,还无法判断token是否匹配,但是随着term()进一步递归、下降,直到factor表达式调用match的时候,才知道token和整个文法匹不匹配。
递归下降的实质是经历了一个对语法树的前序遍历,因为语法树的叶子节点必定是非终结符,也就一定能用match函数进行比对,比对上了就进行下一步,如果没比对上,就退回并选择另一个产生式。
二、
LL(1)是将直接左递归改写成非直接左递归形式,通过first集和follow集的计算求出预测分析表,这样就可以根据读入的token选择对应的产生式。
在LL(1)中,显示维护一个栈结构,就不用产生显示递归,而是用循环(尾递归)来编写程序。
三、实际Tiny语法分析
语法分析部分。
树的结点种类NodeKind分为StmtK和ExpK两类,两类又有各自的子类。
在语法树中标明种类有利于代码生成,当遍历语法树检测到特定类型时就可以进行特定的处理。
treeNode结构为指向孩子和兄弟的节点。
语句通过同属域而不是子域来排序,即由父亲到他的孩子的唯一物理连接是到最左孩子的。孩子则在一个标准连接表中自左向右连接到一起,这种连接称作同属连接(网上查了查,没有这个概念。。),用于区别父子连接。
typedef enum {
StmtK,ExpK} NodeKind;
typedef enum {
IfK,RepeatK,AssignK,ReadK,WriteK} StmtKind;
typedef enum {
OpK,ConstK,IdK} ExpKind;
/* ExpType is used for type checking */
typedef enum