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

目录

一、实验目的

二、实验环境

三、实验内容

四、心得体会


一、实验目的

  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
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 用lex和yacc编写函数绘图语言解释器可以实现对图形绘制命令的解析和执行。Lex(Lexical Analyzer Generator)用于将输入的字符流分解为词法单元,而Yacc(Yet Another Compiler Compiler)则用于生成语法解析。下面是大致的步骤: 1. 首先,需要定义要解析的函数绘图语言的语法规则,包括各种图形绘制命令、表达式、函数等。 2. 使用Lex编写词法分析器部分。根据语法规则,Lex将输入的字符流分解为词法单元,并识别出不同类型的标识符、关键字、运算符等。 3. 使用Yacc编写语法解析部分。定义语法规则,并使用语法规则生成的解析将词法单元组织成抽象语法树(AST),以便对其进行进一步处理。 4. 在生成的解析树上实现图形绘制命令的执行。根据语法规则,在AST上遍历并执行相应的图形绘制操作,如绘制线条、矩形、椭圆等。 5. 添加额外功能。可以在绘图语言中加入变量、循环、判断等控制结构,并在解释器中实现相应的功能,以增加语言的灵活性和扩展性。 通过使用lex和yacc编写函数绘图语言解释器,我们能够实现图形的自定义绘制,可以为用户提供一个简单而强大的绘图环境。此外,lex和yacc是广泛使用的工具,在学习和理解其他编译解释器实现原理方面也具有很大的帮助作用。 ### 回答2: Lex和Yacc是一对强大的工具,可以用于编写函数绘图语言解释器。Lex主要用于将输入的源代码进行词法分析,将其转换成标记流,在词法分析器生成中配置规则,每当匹配到一个模式时,就会生成一个对应的标记。而Yacc则用于语法分析,将标记流转换成以语法规则为基础的抽象语法树。 函数绘图语言解释器的第一步是定义词法分析规则。我们可以使用Lex来配置正则表达式,用于匹配不同的函数绘图语言标记,例如变量、函数名、操作符等。一旦配置完毕,Lex会扫描输入源代码,并将其转换为一系列对应的标记流。 接下来,我们需要使用Yacc来定义语法规则,并构建语法分析器。在Yacc中,我们可以使用“文法规则”来描述语法,每个文法规则包含一个左部和一个右部。左部是一个非终结符,右部由终结符和非终结符组成。通过使用这些文法规则,我们可以构建一个抽象语法树。 在函数绘图语言解释器中,我们可以定义语法规则来表示创建图形、设置画布、绘制线条等操作。当Yacc分析解析标记流时,它会按照语法规则生成相应的抽象语法树。 最后,我们可以使用生成的抽象语法树执行绘图操作。根据具体的语法规则,我们可以在抽象语法树上进行语义分析和执行,以实现绘图功能。例如,根据绘图指令,我们可以调用相应的绘图函数来绘制图形,将结果显示在屏幕上。 综上所述,通过使用Lex和Yacc,我们可以编写一个函数绘图语言解释器。Lex负责词法分析,将源代码转换成标记流,而Yacc负责语法分析,生成抽象语法树,并执行绘图操作。通过这种方式,我们能够解释和执行函数绘图语言的源代码,实现图形的绘制。 ### 回答3: Lex和Yacc是用于构建编程语言解释器的工具。Lex是一个词法分析生成,用于将输入流分解为词法单元(tokens),而Yacc则是一个语法分析生成,用于将词法单元组合成语法树,并执行相应的操作。 为了编写一个函数绘图语言解释器,我们可以使用Lex和Yacc的组合。首先,使用Lex定义函数绘图语言的词法规则。例如,我们可以定义标识符、数字、运算符等词法单元的识别模式,并在识别到这些词法单元时返回相应的标记。 接下来,使用Yacc定义函数绘图语言的语法规则。我们可以定义绘图语言的语法结构,例如函数定义、函数调用、参数列表等,并为每个语法规则编写相应的执行操作。 在词法分析和语法分析过程中,当识别到一个函数调用时,我们可以根据函数名称和参数列表执行相应的绘图操作。绘图操作可以使用其他绘图库或工具进行实现,例如使用Matplotlib进行绘图。 通过Lex和Yacc的组合,我们可以构建一个函数绘图语言解释器,该解释器能够解析输入的函数绘图语句,并执行相应的绘图操作。使用这个解释器,用户可以输入绘图语句,例如函数的定义和调用,然后解释器将解析并执行这些语句,最终生成对应的函数图像。 总而言之,使用Lex和Yacc编写函数绘图语言解释器,可以帮助我们将输入的函数绘图语句解析为可执行的绘图操作,并生成相应的函数图像。这将使用户能够更方便地绘制函数图像,并对函数进行可视化分析。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凉柒-lq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值