【编译原理】自顶向下的递归下降语法分析器 解析

任务:给定一个算术表达式的无关文法,实现一个语法分析器

 

分析:

根据一个上下文无关语法生成一个递归下降的语法分析器需要注意几个方面(思路、步骤)

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);
}
递归下降语法分析器

 

 

 

如果您对文章中的内容有什么疑问,或者有错误的地方,可以在评论区中提出。

如果您觉得这篇文章帮到了您,或者觉得这篇文章写的不错,希望可以点个赞。

如果您觉得这篇文章在某些方面(包括但不限于写法,结构,组织等方面)可以改进,希望可以在评论区中提出的自己的建议。

多谢您的参与!

 

文章所引用的参考资料:

《编译器设计(第二版)》

网易云课堂《编译原理》(华保健)

转载于:https://www.cnblogs.com/HDUjackyan/p/10558571.html

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
递归下降分析法 一、实验目的: 根据某一文法编制调试递归下降分析程序,以便对任意输入的符号串进行分析。本次实验的目的主要是加深对递归下降分析法的理解。 二、实验说明 1、递归下降分析法的功能 词法分析器的功能是利用函数之间的递归调用模拟语法树自上而下的构造过程。 2、递归下降分析法的前提 改造文法:消除二义性、消除递归、提取因子,判断是否为LL(1)文法, 3、递归下降分析法实验设计思想及算法 为G的每个非终结符号U构造一个递归过程,不妨命名为U。 U的产生式的右边指出这个过程的代码结构: (1)若是终结符号,则和向前看符号对照, 若匹配则向前进一个符号;否则出错。 (2)若是非终结符号,则调用与此非终结符对应的过程。当A的右部有多个产生式时,可用选择结构实现。 三、实验要求 (一)准备: 1.阅读课本有关章节, 2.考虑好设计方案; 3.设计出模块结构、测试数据,初步编制好程序。 (二)上课上机: 将源代码拷贝到机上调试,发现错误,再修改完善。第二次上机调试通过。 (三)程序要求: 程序输入/输出示例: 对下列文法,用递归下降分析法对任意输入的符号串进行分析: (1)E->eBaA (2)A->a|bAcB (3)B->dEd|aC (4)C->e|dc 输出的格式如下: (1)递归下降分析程序,编制人:姓名,学号,班级 (2)输入一以#结束的符号串:在此位置输入符号串例如:eadeaa# (3)输出结果:eadeaa#为合法符号串 注意: 1.如果遇到错误的表达式,应输出错误提示信息(该信息越详细越好); 2.对学有余力的同学,可以详细的输出推导的过程,即详细列出每一步使用的产生式。 (四)程序思路 0.定义部分:定义常量、变量、数据结构。 1.初始化:从文件将输入符号串输入到字符缓冲区中。 2.利用递归下降分析分析,对每个非终结符编写函数,在主函数中调用文法开始符号的函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值