函数绘图语言解释器——编译原理课程实验报告

目录

一、实验目的

二、实验环境

三、实验内容

四、心得体会


一、实验目的

  1. 会用正规式和产生式设计简单语言的语法
  2. 会用递归下降子程序编写编译器或解释器
  3. 会撰写简单的技术文档

二、实验环境

Dev_c++ 5.1

三、实验内容

  1. 代码组织框架

 

      2.词法分析器

1.记号种类Token_Type:

ORIGIN,SCALE,ROT,IS,FOR,FROM,TO,STEP,DRAW, //关键字
T //参数
SEMICO,L_BRACKET,R_BRACKET,COMMA,//分隔符
PLUS,MINUS,MUL,DIV,POWER,//运算符
FUNC,//函数
CONST_ID,//常量
NONTOKEN,ERRTOKEN//空符号,出错符

 

2.记号类 Token

Token_Type type,//记号种类
char *lexeme,//记号原始串
double value,//常数的值
double (*func_ptr)(double)//函数指针

 

3.词法分析器类 Scanner

属性:
FILE * InFile;         //输入文件流
unsigned int LineNo;    //记号所在行号
char TokenStr[Token_Maxlen]; //记号缓冲区
​
方法:
MyScanner() { LineNo = 1; };
~MyScanner() {};
int InitScanner(const char *FileName);  //获取文件输入流初始化InFile
void CloseScanner();        //关闭文件
unsigned int GetLineNo();   //获取当前记号所在行号
Token GetToken();       //获取记号
char GetChar();        //从文件输入流中读取一个字符并转为大写
void BackChar(char NextChar);//把字符回退到文件输入流中
void AddToTokenStr(char NextChar);//识别到的记号加入到记号缓冲中
Token InTokenTable(const char *c_str);//判断给定字符串是否在符号表中
void ClearTokenStr();   //清空记号缓冲区

 

4.对核心函数GetToken的设计思路

Token MyScanner::GetToken()

 

a.先过滤掉空白字符和换行,成功读入第一个字符并加入记号缓冲区;

b.如果第一个字符是字母,则可能是关键字,参数,PI,E,读入完成后,看该原始记号串是否在符号表中,存在返回该记号,不存在返回错误记号ErrToken;

c.如果第一个字符是数字,则只能是数(也包含小数),当读入到小数点时,可接着从缓冲区中读小数部分;

d.还可能读入的是运算符,这里读入-可能是减号或注释,/可能是除号或注释,*可能是乘号或幂运算,这些可通过多读一个字符得到唯一确定。其他则是出错记号ErrToken;

    3.语法分析器

1.表达式的语法树节点类型

typedef struct TreeNodeStruct {   
enum Token_Type OpCode; //接收记号的种类
union {
    struct { TreeNodeStruct *left, *right; } CaseOp; //二元运算节点
    struct { TreeNodeStruct *child; func_ptr mathfuncptr; } CaseFunc; //函数调用节点
    double CaseConst;   //常数节点
    double *CasePara;   //参数节点
    } content;
} *TreeNode;

 

2.构造语法树

TreeNode MakeTreeNode(enum  Token_Type opcode, ...);    //构造语法树
void PrintSyntaxTree(TreeNode root, int indent);    //打印语法树

3.递归子程序

//根据BNCF构造的非终结符递归子程序 

//主要产生式的递归子程序,可明显分为两类

//函数绘图语言的语句部分(for,origin,rot,scale语句等)仅进行语法分析,无需构造语法树,因此函数设计为void类型                             

void Program();

void Statement();

virtual void ForStatement();

virtual void OriginStatement();

virtual void RotStatement();

virtual void ScaleStatement();

​

//对表达式进行语法分析时还要为其构造语法树,因此函数返回值设计为指向语法树节点的指针。 

TreeNode Expression();

TreeNode Term();

TreeNode Factor();

TreeNode Component();

TreeNode Atom();

 

4.这里核心是文法的推导和根据所得文法书写递归下降子程序

文法的一步一步优化

G1文法

Program-->ε| Program Statement SEMICO
Statement-->OriginStatement|ScaleStatement|RotStatement|ForStatement
OriginStatement-->ORIGIN IS L_BRACKET Expression COMMA Expression    R_BRACKET
ScaleStatement-->SCALE IS L_BRACKET Expression COMMA Expression R_BRACKET  
RotStatement-->ROT IS Expression   
ForStatement-->FOR T FROM expression TO Expression STEP Expression
            DRAW L_BRACKET Expression COMMA Expression R_BRACKET   
​
Expression-->Expression PLUS Expression    //a+b 
        |Expression MINUS Expression    //a-b 
        |Expression MUL Expression  //a*b 
        |Expression DIV Expression  //a/b 
        |PLUS Expression    //+b 
        |MINUS Expression   //-b 
        |Expression POWER Expression    //a**b 
        |CONST_ID   //常数
        |T
        |FUNC L_BRACKET Expression R_BRACKET    //sin(t)
        |L_BRACKET Expression R_BRACKET //(a)

 

G2 (G1中Expression产生式是二义的,因为它没有区分运算的优先级和结合性,即所有的候选项均具有同等的指导分析的权利,从而造成有些分析步骤的不确定性)

改写文法,根据运算的优先级将Expression的所有候选项分组,具有相同优先级的候选项被分在同一个组里,并为每一个组引入一个非终结符

从上至下,结合性依次升高

表达式中的运算

结合性

非终结符

(二元)PLUS,MINUS

左结合

Expression

MUL,DIV

左结合

Term

(一元) PLUS,MINUS

右结合

Factor

POWER

右结合

Component

(原子表达式)(常数,参数,函数,括号)

Atom

Expression-->Expression PLUS Term    
             |Expression MINUS Term 
             |Term   
Term-->Term MUL Factor
        |Term DIV Factor
        |Factor
Factor-->PLUS Factor
          |MINUS Factor
          |Component
Component-->Atom POWER Component
             |Atom
Atom-->CONST_ID
        |T
        |FUNC L_BRACKET Expression R_BRACKET
        |L_BRACKET Expression R_BRACKET

 

G3(G2中Program,Expression,Term含有左递归,G3是消除左递归和左因子的文法)

Program-->Statement SEMICO Program|ε
Expression-->Term Expression`
Expression`--> PLUS Term Expression`|MINUS Term Expression`|ε
Term-->Factor Term`

 

G4(编写适合递归下降子程序的文法) {},[],|,()

Program-->{Statement SEMICO} 重复0次或若干次
Statement-->OriginStatement|ScaleStatement|RotStatement|ForStatement
OriginStatement-->ORIGIN IS L_BRACKET Expression COMMA Expression R_BRACKET
ScaleStatement-->SCALE IS L_BRACKET Expression COMMA Expression R_BRACKET  
RotStatement-->ROT IS Expression   
ForStatement-->FOR T FROM expression TO Expression STEP Expression
            DRAW L_BRACKET Expression COMMA Expression R_BRACKET   
​
Expression-->Term {(PLUS|MINUS) Term}   
Term-->Factor {(MUL|DIV) Factor}
Factor-->PLUS Factor
        |MINUS Factor
        |Component 
//保持产生式的右递归形式,在程序中通过递归调用实现右边的运算符的先计算
Component-->Atom POWER Component  
            |Atom
Atom-->CONST_ID
        |T
        |FUNC L_BRACKET Expression R_BRACKET
        |L_BRACKET Expression R_BRACKET

 

根据EBNF所得的递归子程序(产生式左部分是函数名,后部分是函数体,根据上述文法中的产生式很容易写出递归程序,这里以两个为例,其他的同理可得)遇到终结符匹配,遇到非终结符递归下降到子程序

void MyParser::ForStatement() {

    //ForStatement ->FOR T FROM Expression TO Expression STEP     //Expression DRAW (Expression,Expression)

TreeNode StartPtr,EndPtr,StepPtr,XPtr,YPtr;

StartPtr=EndPtr=StepPtr=XPtr=YPtr=NULL;//起点,终点,步长横,纵坐标表达式的语法树

    MatchToken(FOR, "FOR");

    MatchToken(T, "T");

    MatchToken(FROM, "FROM");

    StartPtr = Expression();        TreeTrace(StartPtr); 

    Start=GetExprValue(StartPtr);

    MatchToken(TO, "TO");

    EndPtr = Expression();          TreeTrace(EndPtr);

    End=GetExprValue(EndPtr);

    MatchToken(STEP, "STEP");

    StepPtr = Expression();         TreeTrace(StepPtr);

    Step=GetExprValue(StepPtr);

    MatchToken(DRAW, "DRAW");

    MatchToken(L_BRACKET, "(");

    XPtr = Expression();            TreeTrace(XPtr);

    MatchToken(COMMA, ",");

    YPtr = Expression();            TreeTrace(YPtr);

    MatchToken(R_BRACKET, ")"); 

    //DrawLoop(Start,End,Step,x_ptr,y_ptr);

}

//表达式的语法树的递归构造

TreeNode MyParser::Expression() {

    //Expression → Term  { ( PLUS | MINUS) Term } 

    TreeNode left, right;

    Token_Type token_temp;  //当前记号类型

    left = Term();  //左操作数的语法树

    while (token.type == PLUS || token.type == MINUS) //左结合

    {

        token_temp = token.type;

        MatchToken(token_temp); //第一类匹配

        right = Term(); //分析右操作数且得到其语法树

        left = MakeTreeNode(token_temp, left, right);

        // 构造运算的语法树,结果为左子树

    }

    return left;

}

 

4.语义分析器

获取表达式(语法分析数的形式表示)的值

double GetExprValue(TreeNode root){
    if(root==NULL) return 0.0;
    switch(root->OpCode){ //二元运算符
        case PLUS:
            return GetExprValue(root->content.CaseOp.left)+
              GetExprValue(root->content.CaseOp.right);break; 
        case MINUS:
            return GetExprValue(root->content.CaseOp.left)-
              GetExprValue(root->content.CaseOp.right);break;
        case MUL:
            return GetExprValue(root->content.CaseOp.left)*
              GetExprValue(root->content.CaseOp.right);break;
        case DIV:
            return GetExprValue(root->content.CaseOp.left)/
              GetExprValue(root->content.CaseOp.right);break;
        case POWER:
            return pow(GetExprValue(root->content.CaseOp.left),
                       GetExprValue(root->content.CaseOp.right));break;
        case FUNC: //函数
            return(*root->content.CaseFunc.mathfuncptr)
              (GetExprValue(root->content.CaseFunc.child));break;
        case CONST_ID://常量
            return root->content.CaseConst;break;
        case T://参数
            return *(root->content.CasePara);break;
        default://默认返回0
            return 0.0; break;
    }
}

 

四、心得体会

  1. 上机遇到的问题?

①在编写函数绘图语言解释器的过程中,令我感觉到比较难攻克的点是递归下降子程序的编写,因为一开始对文法的推导就是错误的,当时错误的认为语句里出现的所有表达式都可以用expression来统一表示,后来才明白这个文法是二义文法,之后引入新的非终结符消除二义性,消除左递归后才得到真正的递归下降文法。

②当时在文法与程序如何对应上也是思索了一阵,还是之前学的基础知识有所遗忘,在经过了复习之后,其实非常简单,文法中产生式左部对应函数名,产生式右部对应函数体,具体递归下降的规则是:碰到终结符就匹配,碰到非终结符就递归下降到子程序,以此类推。

    2. 优点和缺点?

优点:本实验采用面向对象语言c++编写,面向对象提出了类的概念,这使得我可以将词法,语法,语义分析制作成三个类,每个类有自己的属性和方法(方法实现一定的功能),这可以更好的让我明确词法,语法,语义这三个阶段分别要完成的工作以及他们之间如何传递数据,需要传递哪些数据等等。

不足:本实验不足之处在于界面UI比较简陋,在这方面有一定改进的空间。

下面贴一张词法、语法分析器之间的关联关系:

  • 8
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值