2019.11.8 感谢一位学弟,语法分析器处参考链接数字有误,现已修正。

实验环境:Windows 10,Visual Studio 2017截至本文发表的最新版(2018.12.19)。


1.词法分析器

新建个Visual C++的空项目,然后在头文件、源文件、资源文件依次添加文件-->新建项即可。

编译原理大作业:实现简单的绘图语言——步步高详细指导_绘图语言


编译原理解释器(一)C语言词法分析器的实现 - olive_gyr - 博客园

https://www.cnblogs.com/olivegyr/p/6189753.html


这个文章里代码基本上是可用的,第一个问题是如果使用的是.c文件就不行了,extern、using namespace等会出问题,因为这些涉及C++,所以要用cpp文件,我就因为这一点好久不知原因,被打击够呛。

第二个问题是static Token TokenTab[]里定义的一系列东西,在VS2017中第二列统统会报错为E0144 "const char *" 类型的值不能用于初始化 "char *" 类型的实体。解决方法来自参考资料[1],在第二列前统统加上(char *)。


2.语法分析器


只有参考资料[2]里的代码是不够的,要加上[3]的代码(semantic.cpp):(scannermain用/**/注释掉main的部分,其他继续用)

编译原理大作业:实现简单的绘图语言——步步高详细指导_编译原理_02

依旧有很多(char *)的问题。


3.语义分析器

语法制导翻译,绘图是最后工作,我们用EasyX库。EasyX是一个简单的绘图语言库,支持C++,因此项目里用的都是cpp文件,并非以前常用的c文件,虽然语言还是C语言。


4.项目代码

@@:运行时注意)

EasyX.cpp:

#include "semantic.h"

#include <graphics.h>

 

int main()

{

initgraph(640, 480);

Parser((char*)"5.txt");

closegraph();

return 0;

}

 

scanner.cpp:(词法分析器用到的函数)

#include"scanner.h"

#ifndef MSCANNER_H

#define MSCANNER_H

 

#define TOKEN_LEN 100//设置一个字符缓冲区,这是他的大小用来保留记号的字符串

unsigned int LineNo;//记录字符所在行的行号-》词法分析器对每个记号的字符串进行分析时必须记住该字符串在源程序的位置

static FILE *InFile;//打开绘图语言源程序时,指向该源程序的指针

static char TokenBuffer[TOKEN_LEN];//设置一个字符缓冲区,用来保留记号的字符串,当需要记号的字符串时,char*lexeme指针会指向TokenBuffer

 

//初始化词法分析器

extern int InitScanner(const char *FileName) //输入要分析的源程序文件名

{

LineNo = 1;

InFile = fopen(FileName, "r");

if (InFile != NULL)

return 1;     //如果存在,打开文件,并初始化lineNO的值为1,返回true

else

return 0;    //不存在返回0

}

 

//关闭词法分析器

extern void CloseScanner(void)

{

if (InFile != NULL)

fclose(InFile);

}

 

//从输入源程序中读入一个字符

static char GetChar(void)

{

int Char = getc(InFile);

return toupper(Char);//输出源程序的一个字符,没有输入

}

 

//把预读的字符退回到输入源程序中,分析的过程中需要预读1、2……个字符,预读的字符必须回退,以此保证下次读时不会丢掉字符

static void BackChar(char Char)   //输入:回退一个字符,没有输出

{

if (Char != EOF)

ungetc(Char, InFile);

}

 

//把已经识别的字符加到TokenBuffer

static void AddCharTokenString(char Char)//输入源程序的一个字符,没有输出

{

int TokenLength = strlen(TokenBuffer);//设定好长度

if (TokenLength + 1 >= sizeof(TokenBuffer))

return;//此时字符串的长度超过最大值,返回错误

TokenBuffer[TokenLength] = Char;//添加一个字符

TokenBuffer[TokenLength + 1] = '\0';

}

 

//清空记号缓冲区

static void EmptyTokenString()

{

memset(TokenBuffer, 0, TOKEN_LEN);

}

 

//根据识别的字符串在符号表中查找相应的记号

static Token JudgeKeyToken(const char *IDString)//输入:识别出的字符串;输出:记号

{

int loop;

for (loop = 0; loop < sizeof(TokenTab) / sizeof(TokenTab[0]); loop++)

if (strcmp(TokenTab[loop].lexeme, IDString) == 0)

return TokenTab[loop];//查找成功,返回该记号

Token errortoken;

memset(&errortoken, 0, sizeof(Token));  //清空/初始化errortoken

errortoken.type = ERRTOKEN;

return errortoken;//查找失败,返回错误记号

}

 

//获取一个记号

extern Token GetToken(void)  //次函数由DFA转化而来。此函数输出一个记号。每调用该函数一次,仅仅获得一个记号。

//因此,要获得源程序的所有记号,就要重复调用这个函数。下面声明的函数都被此函数调用过!

//输出一个记号,没有输入

{

Token token;

int Char;

 

memset(&token, 0, sizeof(Token));

EmptyTokenString();//清空缓冲区

token.lexeme = TokenBuffer;//记号的字符指针指向字符缓冲区

for (;;)

{

Char = GetChar();//从源程序中读出一个字符

if (Char == EOF)

{

token.type = NONTOKEN;

return token;

}

if (Char == '\n')

LineNo++;

if (!isspace(Char))

break;

}//end of for

AddCharTokenString(Char);

//若不是空格、TAB、回车、文件结束符等,则先加入到记号的字符缓冲区中

if (isalpha(Char))  //若char是A-Za-z,则一定是函数,关键字、PI、E等

{

for (;;)

{

Char = GetChar();

if (isalnum(Char))

AddCharTokenString(Char);

else

break;

}

BackChar(Char);

token = JudgeKeyToken(TokenBuffer);

token.lexeme = TokenBuffer;

return token;

}

else if (isdigit(Char))  //若是一个数字,则一定是常量

{

for (;;)

{

Char = GetChar();

if (isdigit(Char))

AddCharTokenString(Char);

else

break;

}

if (Char == '.')

{

AddCharTokenString(Char);

for (;;)

{

Char = GetChar();

if (isdigit(Char))

AddCharTokenString(Char);

else

break;

}

}

BackChar(Char);

token.type = CONST_ID;

token.value = atof(TokenBuffer);

return token;

}

else    //不是字母和数字,则一定是运算符或者分隔符

{

switch (Char)

{

case ';':token.type = SEMICO; break;

case '(':token.type = L_BRACKET; break;

case ')':token.type = R_BRACKET; break;

case ',':token.type = COMMA; break;

case '+':token.type = PLUS; break;

case '-':

Char = GetChar();

if (Char == '-')

{

while (Char != '\n'&&HUGE != EOF)

Char = GetChar();

BackChar(Char);

return GetToken();

}

else

{

BackChar(Char);

token.type = MINUS;

break;

}

case '/':

Char = GetChar();

if (Char == '/')

{

while (Char != '\n'&&Char != EOF)

Char = GetChar();

BackChar(Char);

return GetToken();

}

else

{

BackChar(Char);

token.type = DIV;

break;

}

case '*':

Char = GetChar();

if (Char == '*')

{

token.type = POWER;

break;

}

else

{

BackChar(Char);

token.type = MUL;

break;

}

default:token.type = ERRTOKEN; break;

}  //end of switch

}  //end of 运算符或者分隔符

return token;

}//end of GetToken

 

scannermain.cpp:

/*#include "scanner.h"

 

int main()

{

Token token;

char file[] = "0.txt";

if (!InitScanner(file))         //初始化词法分析器

{

printf("Open Sorce File Error !\n");

return 0;

}

printf("记号类别    字符串      常数值     函数指针\n");

printf("--------------------------------------------\n");

while (true)

{

token = GetToken();  //读入一个记号

if (token.type != NONTOKEN)  //记号的类别不是错误,就打印出他的内容

printf("%4d,%12s,%12f,%12x\n", token.type, token.lexeme, token.value, token.FuncPtr);

else

break;

}

printf("-------------------------------------------\n");

CloseScanner();

system("pause");

 

return 0;

}*/

 

 

parser.cpp

#include "parser.h"

 

#ifndef PARSER_DEBUG

#include "semantic.h"

#endif // PARSER_DEBUG

 

double Parameter = 0, Origin_x = 0, Origin_y = 0, Scale_x = 1, Scale_y = 1, Rot_angle = 0;

static Token token;//记号

static void FetchToken();//调用词法分析器的GetToken,把得到的当前记录保存起来。如果得到的记号是非法输入errtoken,则指出一个语法错误

static void MatchToken(enum Token_Type AToken);//匹配当前记号

static void SyntaxError(int case_of);//处理语法错误的子程序。根据错误的性质打印相关信息并且终止程序运行。错误性质可以根据传参不同来区分:SyntaxError(1)词法错   SyntaxError(2)语法错

static void ErrMsg(unsigned LineNo, char *descrip, char *string);  //打印错误信息

static void PrintSyntaxTree(struct ExprNode * root, int indent);  //前序遍历打印树

//非终结符递归子程序声明 有2类

//第1类

//语法分析,不构造语法树,因此语句的子程序均设计为过程->void类型的函数

static void Program();//递归下降分析

static void Statement();

static void OriginStatement();

static void RotStatement();

static void ScaleStatement();

static void ForStatement();

//第2类

//语法分析+构造语法树,因此表达式均设计为返回值为指向语法树节点的指针的函数。

static struct ExprNode *Expression();//二元加减

static struct ExprNode *Term();//乘除

static struct ExprNode *Factor();//一元正负

//把项和因子独立开处理解决了加减号与乘除号的优先级问题。在这几个过程的反复调用中,始终传递fsys变量的值,保证可以在出错的情况下跳过出错的符号,使分析过程得以进行下去。

static struct ExprNode *Component();//幂

static struct ExprNode *Atom();//参数T,函数,括号->一个整体的

 

void call_match(char *x)

{ printf("matchtoken    "); printf(x); printf("\n");  }

 

void Tree_trace(ExprNode *x)

{ PrintSyntaxTree(x, 1);  }

 

//外部接口与语法树构造函数声明

extern void Parser(char* SrcFilePtr);

static struct ExprNode * MakeExprNode(enum Token_Type opcode, ...);//生成语法树的一个节点

 

static void FetchToken()//调用词法分析器的GetToken,把得到的当前记录保存起来。

{

token = GetToken();

if (token.type == ERRTOKEN)

SyntaxError(1); //如果得到的记号是非法输入errtoken,则指出一个语法错误

}

 

//匹配当前的记号

static void MatchToken(enum Token_Type The_Token)

{

if (token.type != The_Token)

SyntaxError(2);//若失败,则指出语法错误

FetchToken();//若成功,则获取下一个

}

 

//语法错误处理

static void SyntaxError(int case_of)

{

switch (case_of)

{

case 1: ErrMsg(LineNo, (char *)"错误记号", token.lexeme);

break;

case 2: ErrMsg(LineNo, (char *)"不是预期记号", token.lexeme);

break;

}

}

 

//打印错误信息

void ErrMsg(unsigned LineNo, char *descrip, char *string)

{

printf("Line No %5d:%s %s !\n", LineNo, descrip, string);

CloseScanner();

exit(1);

}

 

//先序遍历并打印表达式的语法树:根-->左-->右

void PrintSyntaxTree(struct ExprNode *root, int indent)

{

int temp;

for (temp = 1; temp <= indent; temp++)

printf("\t");  //缩进

switch (root->OpCode)  //打印根节点

{

case PLUS:  printf("%s\n", "+"); break;

case MINUS: printf("%s\n", "-"); break;

case MUL:   printf("%s\n", "*"); break;

case DIV:   printf("%s\n", "/"); break;

case POWER: printf("%s\n", "**"); break;

case FUNC:  printf("%x\n", root->Content.CaseFunc.MathFuncPtr); break;

case CONST_ID: printf("%f\n", root->Content.CaseConst); break;

case T:     printf("%s\n", "T"); break;

default:    printf("Error Tree Node!\n"); exit(0);

}

if (root->OpCode == CONST_ID || root->OpCode == T)  //叶子节点返回

return;  //常数和参数只有叶子节点 常数:右值;参数:左值地址

if (root->OpCode == FUNC)  //递归打印一个孩子节点

PrintSyntaxTree(root->Content.CaseFunc.Child, indent + 1);  //函数有孩子节点和指向函数名的指针

else  //递归打印两个孩子节点

{  //二元运算:左右孩子的内部节点

PrintSyntaxTree(root->Content.CaseOperater.Left, indent + 1);

PrintSyntaxTree(root->Content.CaseOperater.Right, indent + 1);

}

}

 

//绘图语言解释器入口(与主程序的外部接口)

void Parser(char *SrcFilePtr)   //语法分析器的入口

{

printf("Enter in Parser\n");

if (!InitScanner(SrcFilePtr))  //初始化词法分析器

{

printf("Open Source File Error !\n");

return;

}

FetchToken();  //获取第一个记号

Program();  //递归下降分析

CloseScanner();  //关闭词法分析器

printf("Exit from Parser\n");

return;

}

 

//Program的递归子程序

static void Program()

{

printf("Enter in Program\n");

while (token.type != NONTOKEN)  //记号类型不是空

{

Statement();  //转到每一种文法

MatchToken(SEMICO);//匹配到分隔符

}

printf("Exit from Program\n");

}

 

//----------Statement的递归子程序

static void Statement()//转到每一种文法

{

printf("Enter in Statement\n");

switch (token.type)

{

case ORIGIN:   OriginStatement(); break;

case SCALE:   ScaleStatement(); break;

case ROT:   RotStatement(); break;

case FOR:   ForStatement(); break;

default:   SyntaxError(2);

}

printf("Exit from Statement");

}

 

//----------OriginStatement的递归子程序

//eg:origin is (20, 200);

static void OriginStatement(void)

{

struct ExprNode *tmp;  //语法树节点的类型

printf("Enter in Original Statement\n");

MatchToken(ORIGIN);

MatchToken(IS);

MatchToken(L_BRACKET);  //eg:origin is (

tmp = Expression();

Origin_x = GetExprValue(tmp);    //获取横坐标点平移距离

DelExprTree(tmp);    //删除一棵树

MatchToken(COMMA);   //eg:,

tmp = Expression();

Origin_y = GetExprValue(tmp);   //获取纵坐标的平移距离

DelExprTree(tmp);

MatchToken(R_BRACKET);    //eg:)

printf("Exit from Origin Statement");

}

 

//----------ScaleStatement的递归子程序

//eg:scale is (40, 40);

static void ScaleStatement(void)

{

struct ExprNode *tmp;

printf("Enter in Scale Statement\n");

MatchToken(SCALE);

MatchToken(IS);

MatchToken(L_BRACKET);     //eg:scale is (

tmp = Expression();

Scale_x = GetExprValue(tmp);   //获取横坐标的比例因子

DelExprTree(tmp);

MatchToken(COMMA);   //eg:,

tmp = Expression();

Scale_y = GetExprValue(tmp);//获取纵坐标的比例因子

DelExprTree(tmp);

MatchToken(R_BRACKET);    //eg:)

printf("Exit from Scale Statement\n");

}

 

//----------RotStatement的递归子程序

//eg:rot is 0;

static void RotStatement(void)

{

struct ExprNode *tmp;

printf("Enter in Rot Statement\n");

MatchToken(ROT);

MatchToken(IS);     //eg:rot is

tmp = Expression();

Rot_angle = GetExprValue(tmp);     //获取旋转角度

DelExprTree(tmp);

printf("Exit from Rot Statement\n");

}

 

//----------ForStatement的递归子程序

//对右部文法符号的展开->遇到终结符号直接匹配,遇到非终结符就调用相应子程序

//ForStatement中唯一的非终结符是Expression,他出现在5个不同位置,分别代表循环的起始、终止、步长、横坐标、纵坐标,需要5个树节点指针保存这5棵语法树

static void ForStatement()

{

//eg:for T from 0 to 2*pi step pi/50 draw (t, -sin(t));

double Start, End, Step;  //绘图起点、终点、步长

struct ExprNode *start_ptr, *end_ptr, *step_ptr, *x_ptr, *y_ptr;  //指向各表达式语法树根节点

printf("Enter in For Statement\n");

//遇到非终结符就调用相应子程序

MatchToken(FOR); call_match((char*)"FOR");

MatchToken(T); call_match((char*)"T");

MatchToken(FROM); call_match((char*)"FROM");  //eg:for T from

start_ptr = Expression();   //获得参数起点表达式的语法树

Start = GetExprValue(start_ptr);   //计算参数起点表达式的值

DelExprTree(start_ptr);   //释放参数起点语法树所占空间  eg:0

MatchToken(TO); call_match((char*)"TO");  //eg:to

end_ptr = Expression();  //构造参数终点表达式语法树

End = GetExprValue(end_ptr);  //计算参数终点表达式的值  eg:2*pi

DelExprTree(end_ptr);   //释放参数终点语法树所占空间

MatchToken(STEP); call_match((char*)"STEP");   //eg:step

step_ptr = Expression();   //构造参数步长表达式语法树

Step = GetExprValue(step_ptr);   //计算参数步长表达式的值  eg:pi/50

DelExprTree(step_ptr);   //释放参数步长语法树所占空间

MatchToken(DRAW);

call_match((char*)"DRAW");

MatchToken(L_BRACKET);

call_match((char*)"(");   //     eg:draw(

x_ptr = Expression();     //根节点  eg:t

MatchToken(COMMA);

call_match((char*)",");   // eg:,

y_ptr = Expression();    //根节点

MatchToken(R_BRACKET);

call_match((char*)")");

DrawLoop(Start, End, Step, x_ptr, y_ptr);       //绘制图形(运行parsermain.cpp时注释掉这行@@

DelExprTree(x_ptr);                             //释放横坐标语法树所占空间

DelExprTree(y_ptr);                             //释放纵坐标语法树所占空间

printf("Exit from For Statement\n");

}

 

//----------Expression的递归子程序

//把函数设计为语法树节点的指针,在函数内引进2个语法树节点的指针变量,分别作为Expression左右操作数(Term)的语法树节点指针

//表达式应该是由正负号或无符号开头、由若干个项以加减号连接而成。

static struct ExprNode* Expression()    //展开右部,并且构造语法树

{

struct ExprNode *left, *right;      //左右子树节点的指针

Token_Type token_tmp;     //当前记号

 

printf("Enter in Expression\n");

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

while (token.type == PLUS || token.type == MINUS)

{

token_tmp = token.type;

MatchToken(token_tmp);

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

left = MakeExprNode(token_tmp, left, right);    //构造运算的语法树,结果为左子树

    Tree_trace(left);   //打印表达式的语法树

}

 

printf("Exit from Expression\n");

return left;       //返回最终表达式的语法树

}

 

//----------Term的递归子程序

//项是由若干个因子以乘除号连接而成

static struct ExprNode* Term()

{

struct ExprNode *left, *right;

Token_Type token_tmp;

left = Factor();

while (token.type == MUL || token.type == DIV)

{

token_tmp = token.type;

MatchToken(token_tmp);

right = Factor();

left = MakeExprNode(token_tmp, left, right);

}

return left;

}

 

//----------Factor的递归子程序

//因子则可能是一个标识符或一个数字,或是一个以括号括起来的子表达式

static struct ExprNode *Factor()

{

struct ExprNode *left, *right;

if (token.type == PLUS)           //匹配一元加运算

{

MatchToken(PLUS);

right = Factor();             //表达式退化为仅有右操作数的表达式

}

else if (token.type == MINUS)

{

MatchToken(MINUS);

right = Factor();

left = new ExprNode;

left->OpCode = CONST_ID;

left->Content.CaseConst = 0.0;

right = MakeExprNode(MINUS, left, right);

}

else

right = Component();         //匹配非终结符Component

return right;

}

 

//----------Component的递归子程序

//幂

static struct ExprNode* Component()//右结合

{

struct ExprNode *left, *right;

left = Atom();

if (token.type == POWER)

{

MatchToken(POWER);

right = Component();         //递归调用Component以实现POWER的右结合

left = MakeExprNode(POWER, left, right);

}

return left;

}

 

//----------Atom的递归子程序

//包括分隔符 函数 常数 参数

static struct ExprNode* Atom()

{

struct Token t = token;

struct ExprNode *address = nullptr, *tmp;

switch (token.type)

{

case CONST_ID:

MatchToken(CONST_ID);

address = MakeExprNode(CONST_ID, t.value);

break;

case T:

MatchToken(T);

address = MakeExprNode(T);

break;

case FUNC:

MatchToken(FUNC);

MatchToken(L_BRACKET);

tmp = Expression();

address = MakeExprNode(FUNC, t.FuncPtr, tmp);

MatchToken(R_BRACKET);

break;

case L_BRACKET:

MatchToken(L_BRACKET);

address = Expression();

MatchToken(R_BRACKET);

break;

default:

SyntaxError(2);

}

return address;

}

 

//----------生成语法树的一个节点

static struct ExprNode *MakeExprNode(enum Token_Type opcode, ...)

{

struct ExprNode *ExprPtr = new(struct ExprNode);

ExprPtr->OpCode = opcode;       //接收记号的类别

va_list ArgPtr;    //声明一个转换参数的变量

va_start(ArgPtr, opcode);   //初始化变量

switch (opcode)     //根据记号的类别构造不同的节点

{

case CONST_ID:     //常数

ExprPtr->Content.CaseConst = (double)va_arg(ArgPtr, double);  //右值

break;

case T:   //参数

ExprPtr->Content.CaseParmPtr = &Parameter;  //左值:地址

break;

case FUNC:  //函数调用

ExprPtr->Content.CaseFunc.MathFuncPtr = (FuncPtr)va_arg(ArgPtr, FuncPtr);  //指向对应函数名的指针 MathFuncPtr

ExprPtr->Content.CaseFunc.Child = (struct ExprNode*)va_arg(ArgPtr, struct ExprNode *);  //孩子的内部节点

break;

default:  //二元运算节点

ExprPtr->Content.CaseOperater.Left = (struct ExprNode *)va_arg(ArgPtr, struct ExprNode *);

ExprPtr->Content.CaseOperater.Right = (struct ExprNode *)va_arg(ArgPtr, struct ExprNode *);

break;

}

va_end(ArgPtr);  //结束变量列表,和va_start成对使用

return ExprPtr;

}

 

parsermain.cpp:(语法分析器测试文件)

/*#include "parser.h"

 

extern void Parser(char *SrcFilePtr);

int main()  //测试主程序

{

Parser((char*)"0.txt");//调用parser进行语法分析,其中被测试的语句在0.txt中

system("pause");

return 0;

}*/

//通过更改的Parser()函数参数来改变要读取的文件

 

 

semantic.cpp

#include "semantic.h"

 

extern  double 

Parameter,//参数T的存储空间:记录t每次加一点的变化,在parser.cpp中声明的

Origin_x, Origin_y,//横纵坐标平移距离

Scale_x, Scale_y,//横纵比例因子

Rot_angle;//旋转角度

 

double GetExprValue(struct ExprNode* root);//获得表达式的值

void DrawLoop(double Start, double End, double Step, struct ExprNode* HorPtr, struct ExprNode* VerPtr);//图形绘制

void DelExprTree(struct ExprNode* root);//删除一棵树

 

static void Errmsg(char *string);//出错处理

static void CalcCoord(struct ExprNode *Hor_Exp, struct ExprNode *Ver_Exp, double &Hor_x, double &Ver_y);//计算点的坐标

 

//出错处理

void Errmsg(char *string)

{  exit(1);  }

 

//计算被绘制点的坐标

static void CalcCoord(struct ExprNode *Hor_Exp,  //横坐标表达式语法树的根节点

struct ExprNode *Ver_Exp,  //纵坐标表达式语法树的根节点

double &Hor_x,  //点横坐标值,起返回值的作用

double &Ver_y)  //点纵坐标值,起返回值的作用

{

double HorCord, VerCord, Hor_tmp;

HorCord = GetExprValue(Hor_Exp);

VerCord = GetExprValue(Ver_Exp);  //根据表达式的语法树计算原始坐标

HorCord *= Scale_x;

VerCord *= Scale_y;   //进行比例变换

Hor_tmp = HorCord * cos(Rot_angle) + VerCord * sin(Rot_angle);

VerCord = VerCord * cos(Rot_angle) - HorCord * sin(Rot_angle);

HorCord = Hor_tmp;    //进行旋转变换

HorCord += Origin_x;

VerCord += Origin_y;   //进行平移变换

Hor_x = HorCord;

Ver_y = VerCord;    //返回变换后点的坐标

}//没有返回值

 

//循环绘制点的坐标

void DrawLoop(double Start,   //起始

double End,               //终止

double Step,              //步长

struct ExprNode* HorPtr,  //横坐标表达式语法树的根节点

struct ExprNode* VerPtr)  //纵坐标表达式语法树的根节点

{

extern double Parameter;

double x, y;

for (Parameter = Start; Parameter <= End; Parameter += Step)//把t在范围内的每一个值带入计算

{

CalcCoord(HorPtr, VerPtr, x, y);  //计算要绘制点的实际坐标

putpixel((int)x, (int)y, RGB(255, 250, 250));  //绘制这个点

}

}

 

//计算表达式的值

double GetExprValue(struct ExprNode *root)  //参数是表达式的根

{  //后续遍历语法树  根据不同的节点类型计算当前根节点的值

if (root == NULL)

return 0.0;

switch (root->OpCode)

{

//二元运算符

case PLUS:

return GetExprValue(root->Content.CaseOperater.Left) + GetExprValue(root->Content.CaseOperater.Right);

case MINUS:

return GetExprValue(root->Content.CaseOperater.Left) - GetExprValue(root->Content.CaseOperater.Right);

case MUL:

return GetExprValue(root->Content.CaseOperater.Left)*GetExprValue(root->Content.CaseOperater.Right);

case DIV:

return GetExprValue(root->Content.CaseOperater.Left) / GetExprValue(root->Content.CaseOperater.Right);

case POWER:

return pow(GetExprValue(root->Content.CaseOperater.Left), GetExprValue(root->Content.CaseOperater.Right));


//函数调用

case FUNC: return (*root->Content.CaseFunc.MathFuncPtr)(GetExprValue(root->Content.CaseFunc.Child));

 

//常数

case CONST_ID: return root->Content.CaseConst;

 

//参数

case T: return *(root->Content.CaseParmPtr);

default: return 0.0;

}  //返回值是表达式的值

}

 

//删除一颗语法树

void DelExprTree(struct ExprNode *root)

{

if (root == NULL)

return;

switch (root->OpCode)

{

case PLUS:       //二元:两个孩子的内部节点

case MINUS:

case MUL:

case DIV:

case POWER:

DelExprTree(root->Content.CaseOperater.Left);

DelExprTree(root->Content.CaseOperater.Right);

break;

case FUNC:       //一元::一个孩子的内部节点

DelExprTree(root->Content.CaseFunc.Child);

break;

default:  break;       //叶子节点

}

delete(root);          //删除节点

}

 

scanner.h:

#define _CRT_SECURE_NO_WARNINGS

#pragma once

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <ctype.h>

#include <stdarg.h>

#include <math.h>

 

enum Token_Type  //枚举记号的类别

{

ORIGIN, SCALE, ROT, IS, TO, STEP, DRAW, FOR, FROM,  //保留字

T,  //参数

SEMICO, L_BRACKET, R_BRACKET, COMMA,  //分隔符

PLUS, MINUS, MUL, DIV, POWER,         //运算符

FUNC,      //函数

CONST_ID,  //常数

NONTOKEN,  //空记号

ERRTOKEN   //出错记号

};

 

typedef double(*MathFuncPtr) (double);

 

struct Token  //记号的数据结构  记号由类别和属性组成

{

Token_Type type;  //记号的类别

char *lexeme;            //属性,原始输入的字符串,是要指向TokenBuffer的指针

double value;            //属性,为常数设置,是常数的值

double(*FuncPtr)(double);//属性,为函数设置,是函数的指针

};

//正规式个数越少越利于程序编写,所以把相同模式的记号共用一个正规式描述,要设计出一个预定义的符号表(就是一个数组),进行区分

static Token TokenTab[] =//符号表(字典):数组元素的类型于记号的类型相同

{//当识别出一个ID时,通过此表来确认具体是哪个记号

{ CONST_ID,    (char*)"PI",         3.1415926,  NULL },

{ CONST_ID,    (char*)"E",          2.71828,    NULL },

{ T,           (char*)"T",          0.0,        NULL },

{ FUNC,        (char*)"SIN",        0.0,        sin  },

{ FUNC,        (char*)"COS",        0.0,        cos  },

{ FUNC,        (char*)"TAN",        0.0,        tan  },

{ FUNC,        (char*) "LN",        0.0,        log  },

{ FUNC,        (char*)"EXP",        0.0,        exp  },

{ FUNC,        (char*) "SQRT",      0.0,        sqrt },

{ ORIGIN,      (char*) "ORIGIN",    0.0,        NULL },

{ SCALE,       (char*) "SCALE",     0.0,        NULL },

{ ROT,         (char*) "ROT",       0.0,        NULL },

{ IS,          (char*)"IS",         0.0,        NULL },

{ FOR,         (char*)"FOR",        0.0,        NULL },

{ FROM,        (char*) "FROM",      0.0,        NULL },

{ TO,          (char*) "TO",        0.0,        NULL },

{ STEP,        (char*)"STEP",       0.0,        NULL },

{ DRAW,        (char*) "DRAW",      0.0,        NULL }

};

 

extern unsigned int LineNo;              //跟踪记好所在源文件行号

extern int InitScanner(const char*);     //初始化词法分析器

extern Token GetToken(void);             //获取记号函数

extern void CloseScanner(void);          //关闭词法分析器

 

parser.h:

#include "scanner.h"

typedef double(*FuncPtr)(double);

 

struct ExprNode        //语法树节点类型

{

enum Token_Type OpCode;  //PLUS,MINUS,MUL,DIV,POWER,FUNC,CONST_ID,T

union

{

//二元运算:只有左右孩子的内部节点

struct { ExprNode *Left, *Right; }CaseOperater;

//函数调用:只有一个孩子的内部节点,还有一个指向对应函数名的指针 MathFuncPtr

struct { ExprNode *Child; FuncPtr MathFuncPtr; }CaseFunc;  


double CaseConst;  //常数:叶子节点  右值

double *CaseParmPtr;  //参数T   左值:存放T值的地址

}Content;

};

 

extern void Parser(char *SrcFilePtr);   //语法分析器对外接口

 

semantic.h:

#include <graphics.h>

#include <conio.h>

#include "parser.h"

 

//----------外部函数声明

extern void DrawPixel(unsigned long x, unsigned long y);//绘制一个点

extern double GetExprValue(struct ExprNode* root);//获得表达式的值

extern void DrawLoop(double Start, double End, double Step, struct ExprNode* HorPtr, struct ExprNode* VerPtr);//图形绘制

extern void DelExprTree(struct ExprNode* root);//删除一棵树

 

全工程文件列表:

编译原理大作业:实现简单的绘图语言——步步高详细指导_C_03

2019.3.3记:学期过去之后懒得再完善了,看见草稿中还有这个,把代码和图补一下就发了,其中的原理大家自己琢磨琢磨,书本知识和代码结合起来思考。


参考资料:

[1] E0144 "const char *" 类型的值不能用于初始化 "char *" 类型的实体 - 一只大鸽子的博客 - CSDN博客

https://blog.csdn.net/qq_41068877/article/details/81272140

[2]编译原理解释器(二)C语言语法分析器的实现 - olive_gyr - 博客园

https://www.cnblogs.com/olivegyr/p/6292604.html

[3]编译原理解释器(三)C语言语义分析器的实现 - olive_gyr - 博客园

https://www.cnblogs.com/olivegyr/p/6292606.html