【编译原理】自顶向下的递归下降语法分析器 解析
任务:给定一个算术表达式的无关文法,实现一个语法分析器
分析:
根据一个上下文无关语法生成一个递归下降的语法分析器需要注意几个方面(思路、步骤):
1.观察给定语法,如果遇到左递归,则需要改写语法来消除左递归
2.根据给定的语法,生成相应符号的First集和Fllow集
3.依照First集和Fllow集实现语法分析器的代码
一、消除左递归:
原本为左递归的语法会使得语法分析器无限循环,无法与给定的输入串进行匹配。我们可以使用左递归将其改写为右递归,该方法是通用的(引入一个新的非终结符进行过渡):
这时候我们将原来的左递归的语法改写成用右递归表示的语法:
二、进行完左递归后,我们要求出新生成语法中相应符号的First集和Fllow集:
该步的目的是让语法分析器产生“预测”的能力,可以根据当前输入中给定的字符来当前语法要进行哪个对应的语法规则转换,从而避免因选择了错误的语法分支而需要进行回溯所带来的性能影响。
接下来讲讲什么是符号的First集和Fllow集。
First集是对所有的符号来说的(包括终结符和非终结符),它所表示的含义是从该符号出发,能够推导出的所有句子(只包含终结符)中,句子开头可能的非终结符有哪些,这些非终结符组成该符号的First集。
而Fllow集则是对所有的终结符(准确的说,是对所有FIrst集中含ε的非终结符来说)来说的,因为其First集中存在ε(即从一个非终结符出发可以不推出任何语句),所以该非终结符根据相应的语法规则推导得出的句子中,该句子的第一个符号不一定存在于它的First集合中(此时该非终结符选择了能推导出ε所在分支的语法,记作语法X),那么要想让他具备“预测”的能力,我们就需要知道在使用了语法X(能推导出ε的语法规则)后,其后面所跟的非终结符可能有哪些,这些非终结符就是所求的Fllow集。
First集求解:
先给出求解First集合的伪代码,再讲解一下求解First集合的步骤:
1.对于终结符的First集合,我们可以直接得到,就是该终结符本身
2.而对于非终结符的First集合,我们需要遍历所有的推导,对于一条推导A -> B1 B2 ... Bn来说,若B1的First集合不含ε,则First(A)= First(B1),否则First(A)还包含First(B2),以此类推,直到当前所出Bi的First集合不含ε为止。这里需要注意一点只有当Bn的First集也含ε时,才将ε加入到A的First集合中
3.直到所有的First集不再变化时(不动点)
通过以上求解,我们可以得到我们所求的语法的First集合,如下表示(将终结符和非终结符区分开来表示):
Fllow集的求解:
同样的,先给出Fllow集合的伪代码,再对步骤进行讲解:
1.对于所有的推导语句A -> B1 B2 ... Bn 来说,首先进行初始化,先将文件终结符EOF加入到所有处于推导语句左侧的非终结符的Fllow集合中 ,表示所有的A语句结束后,后面都可以跟文件终结符EOF
2.对于所有的推导语句A -> B1 B2 ... Bn 来说,我们从后往前求出每个非终结符Bi的Fllow集。对于Bn来说,Fllow(Bn) = Follow(A)。
对于Bn-1来说,如果First(Bn)含ε时,Fllow(Bn-1) = First(Bn)∪Fllow(A);如果First(Bn)不含ε时,Fllow(Bn-1) = First(Bn),以此类推
3.直到所有的Fllow集不再变化时(不动点)
通过以上求解我们得到所有的非终结符的Fllow集如下表示:
三、编写递归下降语法分析器
对于每一条语法的推导都有一个函数实现
有较为详细的错误提示(如:第几个字符出现错误,希望出现XX字符,结果出现了XX字符)
特别注意:什么时候调用其他的解析函数,什么时候报错,什么时候利用First集合和Fllow集合进行预测,以及左右括号匹配的地方
先给出伪代码实现:
再给出我的代码实现:
#include <stdio.h> #include <string.h> #include <stdlib.h> char *str; size_t ind; void Expr(); void Eprime(); void Term(); void Tprime(); void Factor(); void Parse(char *s); void error(char *want, char got) { //report syntax error printf("Compling this expression:\n%s\n", str); printf ("Syntax error at position: %d\n" "\texpecting: %s\n" "\tbut got : %c\n", ind, want, got); //attempt error recovery or exit //exit(0); exit(0); return; } void Expr() { /* Expr -> Term Expr' */ Term(); Eprime(); return; } void Eprime() { /* Expr' -> + Term Expr' */ /* Expr' -> - Term Expr' */ if (str[ind] == '+' || str[ind] == '-') { //先看First集 ind++; Term(); } else if (str[ind] == '\0') { //含ε再看Fllow集 return; } else { //没有得到期望的字符 error("\\0 or + or - or ) ", str[ind]); } return; } void Term() { /* Term -> Factor Term' */ Factor(); Tprime(); return; } void Tprime() { /* Term' -> * Factor Term' */ /* Term' -> / Factor Term' */ if (str[ind] == '*' || str[ind] == '/') { ind++; Factor(); } else if (str[ind] == '\0' || str[ind] == '+' || str[ind] == '-') { return; } else { error("\\0 or * or / or ) or + or -", str[ind]); } return; } void isNumber() { while (str[ind] >= '0' && str[ind] <= '9') { ind++; } } void isName() { while ((str[ind] >= 'a' && str[ind] <= 'z') || (str[ind] >= 'A' && str[ind] <= 'Z')) { ind++; } } void Factor() { /* Factor -> num */ /* Factor -> name */ if (str[ind] >= '0' && str[ind] <= '9') { isNumber(); return; } else if ((str[ind] >= 'a' && str[ind] <= 'z') || (str[ind] >= 'A' && str[ind] <= 'Z')) { isName(); return; } /* Factor -> ( Expr ) */ if (str[ind] == '(') { ind++; Expr(); if (str[ind] != ')') { error("number or name or )", str[ind]); } ind++; } else { error("number or name or (", str[ind]); } } void Parse(char *s) { /* Goal -> Expr */ ind = 0; str = s; Expr(); if (str[ind] != '\0') { error("\\0", str[ind]); } return; } int main( ) { char *e; /* Test */ //e = "(2)"; //Parse(e); //e = "(3+4*5))"; //Parse(e); //e = "(8-2)*3"; //Parse(e); //e = "(8-2)/3"; //Parse(e); }
如果您对文章中的内容有什么疑问,或者有错误的地方,可以在评论区中提出。
如果您觉得这篇文章帮到了您,或者觉得这篇文章写的不错,希望可以点个赞。
如果您觉得这篇文章在某些方面(包括但不限于写法,结构,组织等方面)可以改进,希望可以在评论区中提出的自己的建议。
多谢您的参与!
文章所引用的参考资料:
《编译器设计(第二版)》
网易云课堂《编译原理》(华保健)