编译原理——CMM语法分析器

一、了解BNF范式
   
  巴科斯范式(BNF: Backus-Naur Form 的缩写)是由 John Backus 和 Peter Naur 首先引入的用来描述计算机语言语法的符号集。
  现在,几乎每一位新编程语言书籍的作者都使用巴科斯范式来定义编程语言的语法规则。    
      
巴科斯范式的内容  

在双引号中的字("word")代表着这些字符本身。而double_quote用来代表双引号。
在双引号外的字(有可能有下划线)代表着语法部分。 
尖括号( < > )内包含的为必选项。 
方括号( [ ] )内包含的为可选项。 
大括号( { } )内包含的为可重复0至无数次的项。 
竖线( | )表示在其左右两边任选一项,相当于"OR"的意思。 
::= 是“被定义为”的意思。 

二、分析语法,由于采用递归下降分析,因此先对文法进行分析改变,消除左递归和提取左因子,并对之前的文法进行完善,得到文法


三、对文法进行分析实现

1、创建TreeNode类,里面内嵌许多固定类型,具体设计如下

//program子树用于记录整个程序,所有语句为其孩子节点
public static final int PROGRAM = 0;
//存储变量,value为TOK.ID类型的token,叶节点。
public static final int IDNODE= 1;
//常量节点,value为相应的token类型,叶结点
public static final int CONSTANTNODE= 2;
//value为TOK.BREAK类型的token,叶结点
public static final int BREAKNODE = 3;
//类型节点,value值为类型,孩子节点为数组大小
public static final int TYPENODE= 4;
//声明变量的子树,第一个孩子为TYPENODE,第二个为IDNODE,第三个如果有的话是被赋的值,为expr子树
public static final int DECLARENODE = 5;
//带括号的stmt子树,里面有0到多个stmt,为其children
public static final int STMTBLOCKNODE = 6;
/* 记录if子树,第一个孩子为expr子树,为判断条件,第二个为条件为真的时的语句,stmtblock子树
第三个为条件为假的语句stmtblock子树 */
public static final int IFNODE = 7;
//第一个孩子为expr子树,为判断条件,第二个为循环体,stmtblock子树
public static final int WHILENODE = 8;
//记录赋值语句,第一个孩子结点为value子树,第二个为expr子树
public static final int ASSIGNNODE= 9;
//第一个孩子节点为varNode,其余孩子节点为expr子树,为数组下标
public static final int VALUENODE = 10;
//仅一个子树,表示要读的VALUENODE
public static final int READNODE = 11;
//仅一个子树,表示要读的expr子树
public static final int WRITENODE = 12;
//表示有操作数的子树,value为相应的操作符token,第一个孩子为左操作数,第二个为右操作数
public static final int OPTNODE = 13;
//单目运算符负号,子树为取反的值
public static final int NEGATIVENODE= 14;

private ArrayList<TreeNode> children;//孩子节点数组
private int type;//子树类型
private  Token value;//子树的值
private int priority;//操作符优先级

2、根据first集和follow集进行预测分析,

先对program进行解析,所有的语句都是其孩子节点

public void progarm()throws UnexpectedException{
    //program里面的children里面记录了所有的stmt子树
    programNode.getChildren().add(stmt());
    while (token.getType()!=TOK.EOF){
        programNode.getChildren().add(stmt());
    }
}

Expr类型进行解析,通过符号栈和操作数栈,根据优先级建立抽象语法树

Getexpr函数用于取得操作数,实现 Expr --> - Expr E | Value E | Constant E | (Expr) E 这句文法,

E函数用于取得操作符,实现 E    -->  + Expr E |  Expr E | * Expr E | / Expr E | % Expr E| <= Expr E|< Expr E | > Expr E | >= Expr E | != Expr E | == Expr E|null 这句文法

Expr()函数主要是在最后进行一个栈的计算,完成最后的抽象语法树建立

在建立抽象语法树的时候利用的中缀转后缀的方式,各种节点的优先级定义在Proprity类中。

private void getexpr(Stack<TreeNode> optStack,Stack<TreeNode> numStack,int lparentNum)throws UnexpectedException{
    if(token.getType()==TOK.ID){
        numStack.push(value());//获得值子树,将操作数进栈
    }else if(token.getType()==TOK.LPARENT) {
        TreeNode lparent=new TreeNode(TreeNode.OPTNODE);
        lparent.setPriority(Priority.LPARENT);
        lparent.setValue(token);
        match(TOK.LPARENT);
        optStack.push(lparent);
        lparentNum++;
        getexpr(optStack,numStack,lparentNum);
        if(token.getType()==TOK.RPARENT) {
            if (lparentNum != 0) {
                TreeNode optNode = new TreeNode(TreeNode.OPTNODE);//创建操作符子树
                optNode.setValue(token);
                optNode.setPriority(Priority.RPARENT);
                match(TOK.RPARENT);
                while (optStack.peek().getValue().getType() != TOK.LPARENT) {
                    TreeNode opt = optStack.pop();//符号栈出栈
                    if (opt.getType() != TreeNode.NEGATIVENODE) {//如果是二元操作数
                        TreeNode right = numStack.pop();
                        opt.getChildren().add(numStack.pop());//设置左操作数
                        opt.getChildren().add(right);//设置右操作数
                    } else {
                        opt.getChildren().add(numStack.pop());//一元运算符只有一个操作数
                    }
                    numStack.push(opt);
                }
                optStack.pop();//弹出左括号
                lparentNum--;
            }
        }
    }else if(token.getType()==TOK.MINUS){
        TreeNode negative=new TreeNode(TreeNode.NEGATIVENODE);
        negative.setPriority(Priority.NEGATIVE);
        optStack.push(negative);
        match(TOK.MINUS);
        getexpr(optStack,numStack,lparentNum);
    }else{
        numStack.push(constant());//获得常数子树,并进栈
    }
    E(optStack,numStack,lparentNum);//进一步添加操作数的value和右操作数
}

//设置操作符的value和右操作符
private void E(Stack<TreeNode> optStack,Stack<TreeNode> numStack ,int lparentNum) throws UnexpectedException {
    //expr的follow集为; ] )
    if (token.getType() == TOK.SEMI | token.getType() == TOK.RBRACKET|token.getType() == TOK.RPARENT) {
        //为终结符不做任何操作,等待返回
    }else if(token.getType() == TOK.PLUS|token.getType() == TOK.MINUS|token.getType() == TOK.MUL
            |token.getType() == TOK.DIV|token.getType() == TOK.PERSENT|token.getType() == TOK.LQT
            |token.getType() == TOK.LT|token.getType() == TOK.GQT|token.getType() == TOK.GT
            |token.getType() == TOK.EQ|token.getType() == TOK.NEQ) {
        TreeNode optNode = new TreeNode(TreeNode.OPTNODE);//创建操作符子树
        optNode.setValue(token);
        switch (token.getType()) {
            case TOK.PLUS:
                optNode.setPriority(Priority.PLUS);//设置优先级
                match(TOK.PLUS);
                break;
            case TOK.MINUS:
                optNode.setPriority(Priority.MINUS);
                match(TOK.MINUS);
                break;
            case TOK.MUL:
                optNode.setPriority(Priority.MUL);
                match(TOK.MUL);
                break;
            case TOK.DIV:
                optNode.setPriority(Priority.DIV);
                match(TOK.DIV);
                break;
            case TOK.PERSENT:
                optNode.setPriority(Priority.PERSENT);
                match(TOK.PERSENT);
                break;
            case TOK.LQT:
                optNode.setPriority(Priority.LQT);
                match(TOK.LQT);
                break;
            case TOK.LT:
                optNode.setPriority(Priority.LT);
                match(TOK.LT);
                break;
            case TOK.GQT:
                optNode.setPriority(Priority.GQT);
                match(TOK.GQT);
                break;
            case TOK.GT:
                optNode.setPriority(Priority.GT);
                match(TOK.GT);
                break;
            case TOK.EQ:
                optNode.setPriority(Priority.EQ);
                match(TOK.EQ);
                break;
            case TOK.NEQ:
                optNode.setPriority(Priority.NEQ);
                match(TOK.NEQ);
                break;
        }
        while (optStack.size() != 0 && optStack.peek().getPriority() <= optNode.getPriority()) {
            TreeNode tempNode = optStack.pop();//弹出运算符
            if (tempNode.getType() == TreeNode.NEGATIVENODE) {
                tempNode.getChildren().add(numStack.pop());
            } else {
                TreeNode right = numStack.pop();//弹出右节点
                tempNode.getChildren().add(numStack.pop());//弹出并添加左节点
                tempNode.getChildren().add(right);//添加右节点
            }
            numStack.push(tempNode);//将新的子树压入操作数栈
        }
        optStack.push(optNode);//优先级大的时候直接进栈
        getexpr(optStack,numStack,lparentNum);//继续添加操作数子树
    }else {
        //不是操作符或者终结符,获取操作数
    }
}

 

private TreeNode expr()throws UnexpectedException{
    Stack<TreeNode> optStack=new Stack<>();//符号栈
    Stack<TreeNode> numStack=new Stack<>();//操作数栈
    int lparentNum=0;//左括号的数目
    //先调用操作符进栈,将负号和括号进栈
    getexpr(optStack,numStack,lparentNum);
    while (optStack.size()!=0){
        //当只有负数的时候
        if(optStack.peek().getType()==TreeNode.NEGATIVENODE){
            TreeNode temp=optStack.pop();
            temp.getChildren().add(numStack.pop());
            numStack.push(temp);
        }else{//双目运算符的时候
            TreeNode temp=optStack.pop();
            TreeNode right=numStack.pop();
            temp.getChildren().add(numStack.pop());
            temp.getChildren().add(right);
            numStack.push(temp);
        }
    }
    if(numStack.size()==1){
        return numStack.pop();
    }else{
        throw new UnexpectedException(numStack.pop().getValue(),"表达式出现错误");
    }

}

3,解析变量声明语句

将每个文法实现为一个函数,在varDecl函数中建立varDecl节点,type函数得到的值设置为varDecl节点的第一个孩子,为声明的类型,varList函数得到声明的变量节点,使用curNode和fatherNode实现当一行声明多个变量的时候,创建多个varDecl节点,将其添加在父节点上。

//分析类型节点
private TreeNode type()throws UnexpectedException{
    TreeNode typeNode=new TreeNode(TreeNode.TYPENODE);
    if(token.getType()==TOK.INT){
        typeNode.setValue(token); //声明为int型的子树
        match(TOK.INT);
        //判断时候为数组并设置数组长度
        T(typeNode);
    }else if(token.getType()==TOK.DOUBLE){
        typeNode.setValue(token);//声明为double型的子树
        match(TOK.DOUBLE);
        //判断时候为数组并设置数组长度
        T(typeNode);
    }else {
        throw new UnexpectedException(token,"错误类型,需要正确数据类型");
    }
    return typeNode;
}
//声明变量表子树
private void varList(TreeNode fatherNode)throws UnexpectedException{
    TreeNode varNode=new TreeNode(TreeNode.IDNODE);//声明变量类型子树
    varNode.setValue(token);//value值为ID类型token
    curNode.getChildren().add(varNode);
    match(TOK.ID);
    //如果声明时发生赋值
    if(token.getType()==TOK.ASSIGN){
        match(TOK.ASSIGN);
        curNode.getChildren().add(expr());
    }
    //当一行声明多个多个变量的时候创建多个DECLARENODE添加到父节点上
    while (token.getType()==TOK.COMMA){
        match(TOK.COMMA);
        TreeNode newDeclNode=new TreeNode(TreeNode.DECLARENODE);
        //将当前的类型赋给同时声明的变量
        newDeclNode.getChildren().add((TreeNode) curNode.getChildren().get(0).clone());
        TreeNode moreVar=new TreeNode(TreeNode.IDNODE);
        moreVar.setValue(token);
        newDeclNode.getChildren().add(moreVar);
        match(TOK.ID);
        //如果声明时发生赋值
        if(token.getType()==TOK.ASSIGN){
            match(TOK.ASSIGN);
            curNode.getChildren().add(expr());
        }
        fatherNode.getChildren().add(newDeclNode);//将多个声明的节点添加到父节点上
    }
}
//变量声明
private TreeNode varDecl(TreeNode fatherNode)throws UnexpectedException{
    //创建变量声明子树
    TreeNode varDecl=new TreeNode(TreeNode.DECLARENODE);
    curNode=varDecl;//变更当前处理的子树
    varDecl.getChildren().add(type());//添加相应数据类型的数
    varList(fatherNode);//将做子树作为声明的变量中
    if(token.getType()==TOK.SEMI){
        match(TOK.SEMI);
    }else {
        throw new UnexpectedException(token,"未正确结尾,需要‘;’");
    }
    return varDecl;
}

这样建立的声明数的一个缺点是第一个声明的位置在孩子数组的最后一个,其余位置和声明顺序相同

4、其余语句比较简单,依照递归下降法进行语法分析及添加节点就好,具体分析可以看代码注释

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值