编译原理实验——简单函数绘图语言的解释器


编译原理布置了一个巨复杂的作业,完成一个简单函数绘图语言的解释器,秉承某电实验班的开源精神~在此记录一下方便后人。


运行环境:Visual Studio 2019
简单函数绘图语言的解释器可分为:词法分析器、语法分析器、语义分析器三部分实现。代码组织框架如下图所示:

1、词法分析器

测试文本:
test_scanner.txt

ORIGIn IS (350, 220);  
rot is 0;
scale is (50, 100);
FOR T FROM 0 TO 2*PI STEP PI/500 DRAW(cos(T),sin(T));
scale is (100, 100);
FOR T FROM 0 TO 2*PI STEP PI/500 DRAW(cos(2*T),sin(T));

scanner.h

#ifndef SCANNER_H
#define SCANNER_H

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> //ctype.h是C标准函数库中的头文件,定义了一批C语言字符分类函数,用于测试字符是否属于特定的字符类别,
//如字母字符、控制字符等等。
#include <stdarg.h>//让函数可以接收可变参数
#include <math.h>

enum Token_Type//记号类别枚举
{
	ORIGIN/* 0 */, SCALE/* 1 */, ROT/* 2 */, IS/* 3 */, TO/* 4 */, STEP/* 5 */, DRAW/* 6 */, FOR/* 7 */, FROM/* 8 */,//保留字
	T/* 9 */,//参数
	SEMICO/* 10 */, L_BRACKET/* 11 */, R_BRACKET/* 12 */, COMMA/* 13 */,//分层符号
	PLUS/* 14 */, MINUS/* 15 */, MUL/* 16 */, DIV/* 17 */, POWER/* 18 */,//运算符
	FUNC/*19 */,//函数
	CONST_ID/* 20 */,//常数
	NONTOKEN/* 21 */,//空记号,标记源程序文件的结束
	ERRTOKEN/* 22 *///出错记号,标记非法输入
};

typedef double(*MathFuncPtr)(double);//MathFuncPtr代表一个指向返回double值并带有一个double形参的函数的指针的类型

struct Token
{
	Token_Type type;//记号类别
	char * lexeme;//属性,字符串,指向char类型的指针
	double value;//属性,常数的值,double型
	double(*FuncPtr)(double);//属性,函数指针,代表一个指向返回double值并带有一个double形参的函数的指针的类型
};

static Token TokenTab[] = //符号表内容
{
	{ 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);//关闭词法分析器

#endif#pragma once
#pragma once

scanner.cpp

#include "scanner.h"
#define TOKEN_LEN 100//记号最大长度
#pragma warning(disable: 4996)

unsigned int LineNo;//源文件行号
static FILE *InFile;//输入文件流
static char TokenBuffer[TOKEN_LEN];//记号字符缓冲

//初始化词法分析器
extern int InitScanner(const char *FileName)
{
	LineNo = 1;//行号
	InFile = fopen(FileName, "r");//只读形式打开文件
	if (InFile != NULL) return 1;//打开成功
	else return 0;//打开失败
}

//关闭词法分析器
extern void CloseScanner(void)
{
	if (InFile != NULL)
		fclose(InFile);//关闭文件
}

//从输入源程序(流)读字符
static char GetChar(void)
{
	char Char = getc(InFile);//逐个字符读
	return toupper(Char);//全部转换为大写,然后return
}

//将预读字符退回源程序(流)
static void BackChar(char Char)
{
	if (Char != EOF)
		ungetc(Char, InFile);
}

//加入字符到记号缓冲区
static void AddCharTokenString(char Char)
{
	int TokenLength = strlen(TokenBuffer);
	if (TokenLength + 1 >= sizeof(TokenBuffer)) return;//数组越界
	TokenBuffer[TokenLength] = Char;//写入字符到缓冲区
	TokenBuffer[TokenLength + 1] = '\0';//加入字符串结束标志“\0”
}

//请空记号缓冲区
static void EmptyTokenString()
{
	memset(TokenBuffer, 0, TOKEN_LEN);
}

//判断记号是否在记号表中
static Token JudgeKeyToken(const char *IDString)
{
	int loop;
	//sizeof(TokenTab)L:表示这个数组一共占了多少字节数;sizeof(TokenTab[0]):表示一个元素所占的字节数,两者相除,表述数组中一共有多少个元素
	for (loop = 0; loop < sizeof(TokenTab) / sizeof(TokenTab[0]); loop++)
	{	//遍历TokenTab表
		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)
{
	Token token;
	char Char;
	memset(&token, 0, sizeof(Token));//token置空
	EmptyTokenString();//清空记号缓冲区
	token.lexeme = TokenBuffer;
	for (;;)//此循环用来过滤掉源程序中的空格、TAB、回车等分隔符,文件结束返回空记号
	{
		Char = GetChar();//从源程序读字符
		if (Char == EOF)//读出错
		{
			token.type = NONTOKEN;
			return token;
		}
		if (Char == '\n')
			LineNo++;//行号+1
		if (!isspace(Char))//遇到空格该记号肯定已经完成,退出循环
			break;
	}
	AddCharTokenString(Char);//如果不是上面的那些分隔符,就先加入缓冲区
	if (isalpha(Char))//如果是英文字母,一定是函数、关键字、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' && Char != 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;
		}
	}
	return token;
}

test_scanner.cpp

#include "scanner.h"

int main()
{
	Token token;
	FILE *fp;
	InitScanner("test_scanner.txt");
	printf("记号类型    字符串     常数值     函数指针\n");
	printf("------------------------------------------\n");
	while (1)
	{
		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();
}

2、语法分析器

测试文本:
test_parser.txt

ORIGIn IS (350, 220);  
rot is 0;
scale is (50, 100);
FOR T FROM 0 TO 2*PI STEP PI/500 DRAW(cos(T),sin(T));
scale is (100, 100);
FOR T FROM 0 TO 2*PI STEP PI/500 DRAW(cos(2*T),sin(T));

Parser.h

#ifndef PARSER_H
#define 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;     //二元运算:只有左右孩子的内部节点
		struct { ExprNode *Child; FuncPtr MathFuncPtr; }CaseFunc;//函数调用:只有一个孩子的内部节点,还有一个指向对应函数名的指针 MathFuncPtr
		double CaseConst;                                   //常数:叶子节点  右值
		double *CaseParmPtr;                                //参数T   左值:存放T的值得地址
	}Content;
};

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

#endif #pragma once
#pragma once

Scanner.h

#ifndef SCANNER_H
#define SCANNER_H

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> //ctype.h是C标准函数库中的头文件,定义了一批C语言字符分类函数,用于测试字符是否属于特定的字符类别,
//如字母字符、控制字符等等。
#include <stdarg.h>//让函数可以接收可变参数
#include <math.h>

enum Token_Type//记号类别枚举
{
	ORIGIN/* 0 */, SCALE/* 1 */, ROT/* 2 */, IS/* 3 */, TO/* 4 */, STEP/* 5 */, DRAW/* 6 */, FOR/* 7 */, FROM/* 8 */,//保留字
	T/* 9 */,//参数
	SEMICO/* 10 */, L_BRACKET/* 11 */, R_BRACKET/* 12 */, COMMA/* 13 */,//分层符号
	PLUS/* 14 */, MINUS/* 15 */, MUL/* 16 */, DIV/* 17 */, POWER/* 18 */,//运算符
	FUNC/*19 */,//函数
	CONST_ID/* 20 */,//常数
	NONTOKEN/* 21 */,//空记号,标记源程序文件的结束
	ERRTOKEN/* 22 *///出错记号,标记非法输入
};

typedef double(*MathFuncPtr)(double);//MathFuncPtr代表一个指向返回double值并带有一个double形参的函数的指针的类型

struct Token
{
	Token_Type type;//记号类别
	char * lexeme;//属性,字符串,指向char类型的指针
	double value;//属性,常数的值,double型
	double(*FuncPtr)(double);//属性,函数指针,代表一个指向返回double值并带有一个double形参的函数的指针的类型
};

static Token TokenTab[] = //符号表内容
{
	{ 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);//关闭词法分析器

#endif#pragma once
#pragma once

Parser.cpp

#include"parser.h"
//#include"semantic.h"
#include<Windows.h>
#define Tree_trace(x) PrintSyntaxTree(x,1);
#pragma warning(disable: 4996)
#pragma warning(disable: 4703)
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 The_Token);//匹配当前记号
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();//Origin语句
static void RotStatement();//Rot语句
static void ScaleStatement();//Scale语句
static void ForStatement();//For语句
						   //第2类,语法分析+构造语法树,因此表达式均设计为返回值为指向语法树节点的指针的函数。
static struct ExprNode *Expression();//表达式、二元加减运算表达式
static struct ExprNode *Term();//乘除运算表达式
static struct ExprNode *Factor();//一元加减运算表达式
								 //把项和因子独立开处理解决了加减号与乘除号的优先级问题。在这几个过程的反复调用中,始终传递fsys变量的值,保证可以在出错的情况下跳过出错的符号,使分析过程得以进行下去。
static struct ExprNode *Component();//幂运算表达式
static struct ExprNode *Atom();//原子表达式

							   //对外接口声明
extern void Parser(char *SrcFilePtr);

//语法树构造函数声明
static struct ExprNode *MakeExprNode(enum Token_Type opcode, ...);

//通过词法分析器接口GetToken获得一个记号
static void FetchToken()
{
	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)
{
	char msg[256];
	memset(msg, 0, 256);
	sprintf(msg, "Line No %5d:%s %s !", LineNo, descrip, string);

	MessageBoxA(NULL, msg, "error!", MB_OK);

	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)//语法分析器的入口
{
	if (!InitScanner(SrcFilePtr))//初始化词法分析器
	{
		printf("Open Source File Error !\n"); return;
	}
	FetchToken();//先获得一个记号
	Program();//然后进入Program递归子程序,递归下降分析
	CloseScanner();//关闭词法分析器
	return;
}

//程序Program的递归子程序
static void Program()
{
	while (token.type != NONTOKEN)//记号类型不为空
	{
		Statement();//程序有多个语句
		MatchToken(SEMICO);//直到匹配到分隔符
	}
}

//语句Statment的递归子程序
static void Statement()
{
	switch (token.type)
	{
		//罗列四种语句类型,分别分析
	case ORIGIN: OriginStatement(); break;
	case SCALE: ScaleStatement(); break;
	case ROT:  RotStatement(); break;
	case FOR: ForStatement(); break;
	default: SyntaxError(2); //否则报错
	}
}

//语句OriginStatement的递归子程序
static void OriginStatement(void)
{
	struct ExprNode *tmp;//语法树节点的类型
	MatchToken(ORIGIN);
	MatchToken(IS);
	MatchToken(L_BRACKET);

	tmp = Expression();  Tree_trace(tmp);

	//Origin_x = GetExprValue(tmp);//获取横坐标点平移距离
	//DelExprTree(tmp);//删除一棵树

	MatchToken(COMMA);

	tmp = Expression();   Tree_trace(tmp);

	//Origin_y = GetExprValue(tmp);//获取纵坐标点平移距离
	//DelExprTree(tmp);//删除一棵树

	MatchToken(R_BRACKET);
}

//语句ScaleStatement的递归子程序
static void ScaleStatement(void)
{
	struct ExprNode *tmp;
	MatchToken(SCALE);
	MatchToken(IS);
	MatchToken(L_BRACKET);

	tmp = Expression();  Tree_trace(tmp);

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

	MatchToken(COMMA);

	tmp = Expression();    Tree_trace(tmp);

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

	MatchToken(R_BRACKET);
}

//语句RotStatement的递归子程序
static void RotStatement(void)
{
	struct ExprNode *tmp;
	MatchToken(ROT);
	MatchToken(IS);

	tmp = Expression();  Tree_trace(tmp);
	
	//Rot_angle = GetExprValue(tmp);//获得旋转角度
	//DelExprTree(tmp);
}

//语句ForStatement的递归子程序
//对右部文法符号的展开->遇到终结符号直接匹配,遇到非终结符就调用相应子程序
//ForStatement中唯一的非终结符是Expression,他出现在5个不同位置,分别代表循环的起始、终止、步长、横坐标、纵坐标,需要5个树节点指针保存这5棵语法树
static void ForStatement(void)
{
	//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;

	MatchToken(FOR);
	MatchToken(T);
	MatchToken(FROM);//eg:for T from

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

	//Start = GetExprValue(start_ptr);//计算参数起点表达式的值
	//DelExprTree(start_ptr);//释放参数起点语法树所占空间

	MatchToken(TO);

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

	//End = GetExprValue(end_ptr);//计算参数终点表达式的值
	//DelExprTree(end_ptr);//释放参数终点语法树所占空间

	MatchToken(STEP);

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

	//Step = GetExprValue(step_ptr);//计算参数步长表达式的值
	//DelExprTree(step_ptr);//释放参数步长语法树所占空间

	MatchToken(DRAW);
	MatchToken(L_BRACKET);

	x_ptr = Expression(); Tree_trace(x_ptr);

	MatchToken(COMMA);

	y_ptr = Expression();Tree_trace(y_ptr);

	MatchToken(R_BRACKET);

	//DrawLoop(Start, End, Step, x_ptr, y_ptr); //绘制图形
	//DelExprTree(x_ptr);//释放横坐标语法树所占空间
	//DelExprTree(y_ptr);//释放纵坐标语法树所占空间
}

//(二元加减运算)表达式Expression的递归子程序,与上边不太相同的是,表达式需要为其构造语法树
//把函数设计为语法树节点的指针,在函数内引进2个语法树节点的指针变量,分别作为Expression左右操作数(Term)的语法树节点指针
//表达式应该是由正负号或无符号开头、由若干个项以加减号连接而成。
static struct ExprNode *Expression()//展开右部,并且构造语法树
{
	struct ExprNode *left, *right;//左右子树节点指针
	Token_Type token_tmp;//当前记号

	left = Term();//分析左操作数得到其语法树,左操作数是一个乘除运算表达式
	while (token.type == PLUS || token.type == MINUS)
	{
		token_tmp = token.type;//当前记号是加/减
		MatchToken(token_tmp);//匹配记号
		right = Term();//分析右操作数得到其语法树,右操作数是一个乘除运算表达式
		left = MakeExprNode(token_tmp, left, right);//构造运算的语法树,结果为左子树
	}
	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, *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();  Tree_trace(tmp);
		address = MakeExprNode(FUNC, t.FuncPtr, tmp);
		MatchToken(R_BRACKET);
		break;
	case L_BRACKET:
		MatchToken(L_BRACKET);
		address = Expression();  Tree_trace(address);
		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);//初始化va_list变量,第一个参数也就是固定参数为opcode
	switch (opcode)//根据记号的类别构造不同的节点
	{
	case CONST_ID://常数节点
		ExprPtr->Content.CaseConst = (double)va_arg(ArgPtr, double);//返回可变参数,可变参数类型是常数
		break;
	case T://参数T节点
		ExprPtr->Content.CaseParmPtr = &Parameter;//返回可变参数,可变参数类型是参数T
		break;
	case FUNC://函数调用节点
		ExprPtr->Content.CaseFunc.MathFuncPtr = (FuncPtr)va_arg(ArgPtr, FuncPtr);//可变参数类型是对应函数的指针
		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);//结束可变参数的获取

	return ExprPtr;
}

Scanner.cpp

#include "scanner.h"
#define TOKEN_LEN 100//记号最大长度
#pragma warning(disable: 4996)

unsigned int LineNo;//源文件行号
static FILE *InFile;//输入文件流
static char TokenBuffer[TOKEN_LEN];//记号字符缓冲

//初始化词法分析器
extern int InitScanner(const char *FileName)
{
	LineNo = 1;//行号
	InFile = fopen(FileName, "r");//只读形式打开文件
	if (InFile != NULL) return 1;//打开成功
	else return 0;//打开失败
}

//关闭词法分析器
extern void CloseScanner(void)
{
	if (InFile != NULL)
		fclose(InFile);//关闭文件
}

//从输入源程序(流)读字符
static char GetChar(void)
{
	char Char = getc(InFile);//逐个字符读
	return toupper(Char);//全部转换为大写,然后return
}

//将预读字符退回源程序(流)
static void BackChar(char Char)
{
	if (Char != EOF)
		ungetc(Char, InFile);
}

//加入字符到记号缓冲区
static void AddCharTokenString(char Char)
{
	int TokenLength = strlen(TokenBuffer);
	if (TokenLength + 1 >= sizeof(TokenBuffer)) return;//数组越界
	TokenBuffer[TokenLength] = Char;//写入字符到缓冲区
	TokenBuffer[TokenLength + 1] = '\0';//加入字符串结束标志“\0”
}

//请空记号缓冲区
static void EmptyTokenString()
{
	memset(TokenBuffer, 0, TOKEN_LEN);
}

//判断记号是否在记号表中
static Token JudgeKeyToken(const char *IDString)
{
	int loop;
	//sizeof(TokenTab)L:表示这个数组一共占了多少字节数;sizeof(TokenTab[0]):表示一个元素所占的字节数,两者相除,表述数组中一共有多少个元素
	for (loop = 0; loop < sizeof(TokenTab) / sizeof(TokenTab[0]); loop++)
	{	//遍历TokenTab表
		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)
{
	Token token;
	char Char;
	memset(&token, 0, sizeof(Token));//token置空
	EmptyTokenString();//清空记号缓冲区
	token.lexeme = TokenBuffer;
	for (;;)//此循环用来过滤掉源程序中的空格、TAB、回车等分隔符,文件结束返回空记号
	{
		Char = GetChar();//从源程序读字符
		if (Char == EOF)//读出错
		{
			token.type = NONTOKEN;
			return token;
		}
		if (Char == '\n')
			LineNo++;//行号+1
		if (!isspace(Char))//遇到空格该记号肯定已经完成,退出循环
			break;
	}
	AddCharTokenString(Char);//如果不是上面的那些分隔符,就先加入缓冲区
	if (isalpha(Char))//如果是英文字母,一定是函数、关键字、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' && Char != 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;
		}
	}
	return token;
}

test_parser.cpp

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"parser.h"
#pragma warning(disable:4996)
extern void Parser( char *SrcFilePtr);
int main(int argc,  char *argv[])
{
	char*p;
	p = (char*)malloc(20 * sizeof(char));
	memset(p, 0, sizeof(char) * 20);
	strcpy(p, "test_parser.txt");
	Parser(p);
	free(p);
	system("pause");
}

3、语义分析器及绘图

测试文本:
test2.txt

--------------- 图形1:
origin is (20, 200);									-- 设置原点的偏移量
rot is 0;												-- 不旋转
scale is (40, 40);										-- 设置比例
for T from 0 to 2*pi step pi/50 draw (t, -sin(t));		-- 画T的轨迹
origin is (20, 240);									-- 下移40单位
for T from 0 to 2*pi step pi/50 draw (t, -sin(t));		-- 画T的轨迹
origin is (20, 280);									-- 再下移40单位
for T from 0 to 2*pi step pi/50 draw (t, -sin(t));		-- 画T的轨迹

--------------- 图形2:
origin is (380, 240);									-- 右移
scale is (80, 80/3);									-- y方向压缩为三分之一
rot is pi/2+0*pi/3 ;									-- 旋转初值设置
for T from -pi to pi step pi/50 draw (cos(t), sin(t));	-- 画T的轨迹
rot is pi/2+2*pi/3;										-- 旋转2/3*pi
for T from -pi to pi step pi/50 draw (cos(t), sin(t));	-- 画T的轨迹
rot is pi/2-2*pi/3;										-- 再旋转2/3*pi
for T from -pi to pi step pi/50 draw (cos(t), sin(t));	-- 画T的轨迹

--------------- 图形3:
origin is(580, 240);									-- 再右移
scale is (80, 80);										-- 恢复原比例
rot is 0;												-- 不旋转
for t from 0 to 2*pi step pi/50 draw(cos(t),sin(t));	-- 画T的轨迹
for t from 0 to Pi*20 step Pi/50 draw					-- 画T的轨迹
   ((1-1/(10/7))*Cos(T)+1/(10/7)*Cos(-T*((10/7)-1)), 
	(1-1/(10/7))*Sin(T)+1/(10/7)*Sin(-T*((10/7)-1)));

Semantic.h

#include<windows.h>
#include<wingdi.h>

//“VC_Compiler”是用windows自带图形库实现的词法分析器,程序结果输出函数绘图语言解释器绘出的图像
#include"parser.h"

#define red RGB(255,0,0)//红色
#define black RGB(0,0,0)//黑色

//外部函数声明
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);//删除一棵树#pragma once
#pragma once

Scanner.h

#ifndef SCANNER_H
#define SCANNER_H

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> //ctype.h是C标准函数库中的头文件,定义了一批C语言字符分类函数,用于测试字符是否属于特定的字符类别,
//如字母字符、控制字符等等。
#include <stdarg.h>//让函数可以接收可变参数
#include <math.h>

enum Token_Type//记号类别枚举
{
	ORIGIN/* 0 */, SCALE/* 1 */, ROT/* 2 */, IS/* 3 */, TO/* 4 */, STEP/* 5 */, DRAW/* 6 */, FOR/* 7 */, FROM/* 8 */,//保留字
	T/* 9 */,//参数
	SEMICO/* 10 */, L_BRACKET/* 11 */, R_BRACKET/* 12 */, COMMA/* 13 */,//分层符号
	PLUS/* 14 */, MINUS/* 15 */, MUL/* 16 */, DIV/* 17 */, POWER/* 18 */,//运算符
	FUNC/*19 */,//函数
	CONST_ID/* 20 */,//常数
	NONTOKEN/* 21 */,//空记号,标记源程序文件的结束
	ERRTOKEN/* 22 *///出错记号,标记非法输入
};

typedef double(*MathFuncPtr)(double);//MathFuncPtr代表一个指向返回double值并带有一个double形参的函数的指针的类型

struct Token
{
	Token_Type type;//记号类别
	char * lexeme;//属性,字符串,指向char类型的指针
	double value;//属性,常数的值,double型
	double(*FuncPtr)(double);//属性,函数指针,代表一个指向返回double值并带有一个double形参的函数的指针的类型
};

static Token TokenTab[] = //符号表内容
{
	{ 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);//关闭词法分析器

#endif#pragma once
#pragma once

Parser.h

#ifndef PARSER_H
#define 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;     //二元运算:只有左右孩子的内部节点
		struct { ExprNode *Child; FuncPtr MathFuncPtr; }CaseFunc;//函数调用:只有一个孩子的内部节点,还有一个指向对应函数名的指针 MathFuncPtr
		double CaseConst;                                   //常数:叶子节点  右值
		double *CaseParmPtr;                                //参数T   左值:存放T的值得地址
	}Content;
};

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

#endif #pragma once
#pragma once

Scanner.cpp

#include "scanner.h"
#define TOKEN_LEN 100//记号最大长度
#pragma warning(disable: 4996)

unsigned int LineNo;//源文件行号
static FILE *InFile;//输入文件流
static char TokenBuffer[TOKEN_LEN];//记号字符缓冲

//初始化词法分析器
extern int InitScanner(const char *FileName)
{
	LineNo = 1;//行号
	InFile = fopen(FileName, "r");//只读形式打开文件
	if (InFile != NULL) return 1;//打开成功
	else return 0;//打开失败
}

//关闭词法分析器
extern void CloseScanner(void)
{
	if (InFile != NULL)
		fclose(InFile);//关闭文件
}

//从输入源程序(流)读字符
static char GetChar(void)
{
	char Char = getc(InFile);//逐个字符读
	return toupper(Char);//全部转换为大写,然后return
}

//将预读字符退回源程序(流)
static void BackChar(char Char)
{
	if (Char != EOF)
		ungetc(Char, InFile);
}

//加入字符到记号缓冲区
static void AddCharTokenString(char Char)
{
	int TokenLength = strlen(TokenBuffer);
	if (TokenLength + 1 >= sizeof(TokenBuffer)) return;//数组越界
	TokenBuffer[TokenLength] = Char;//写入字符到缓冲区
	TokenBuffer[TokenLength + 1] = '\0';//加入字符串结束标志“\0”
}

//请空记号缓冲区
static void EmptyTokenString()
{
	memset(TokenBuffer, 0, TOKEN_LEN);
}

//判断记号是否在记号表中
static Token JudgeKeyToken(const char *IDString)
{
	int loop;
	//sizeof(TokenTab)L:表示这个数组一共占了多少字节数;sizeof(TokenTab[0]):表示一个元素所占的字节数,两者相除,表述数组中一共有多少个元素
	for (loop = 0; loop < sizeof(TokenTab) / sizeof(TokenTab[0]); loop++)
	{	//遍历TokenTab表
		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)
{
	Token token;
	char Char;
	memset(&token, 0, sizeof(Token));//token置空
	EmptyTokenString();//清空记号缓冲区
	token.lexeme = TokenBuffer;
	for (;;)//此循环用来过滤掉源程序中的空格、TAB、回车等分隔符,文件结束返回空记号
	{
		Char = GetChar();//从源程序读字符
		if (Char == EOF)//读出错
		{
			token.type = NONTOKEN;
			return token;
		}
		if (Char == '\n')
			LineNo++;//行号+1
		if (!isspace(Char))//遇到空格该记号肯定已经完成,退出循环
			break;
	}
	AddCharTokenString(Char);//如果不是上面的那些分隔符,就先加入缓冲区
	if (isalpha(Char))//如果是英文字母,一定是函数、关键字、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' && Char != 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;
		}
	}
	return token;
}

Parser.cpp

#include"parser.h"
#include"semantic.h"
//#define Tree_trace(x) PrintSyntaxTree(x,1);
#pragma warning(disable: 4996)
#pragma warning(disable: 4703)
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 The_Token);//匹配当前记号
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();//Origin语句
static void RotStatement();//Rot语句
static void ScaleStatement();//Scale语句
static void ForStatement();//For语句
						   //第2类,语法分析+构造语法树,因此表达式均设计为返回值为指向语法树节点的指针的函数。
static struct ExprNode *Expression();//表达式、二元加减运算表达式
static struct ExprNode *Term();//乘除运算表达式
static struct ExprNode *Factor();//一元加减运算表达式
								 //把项和因子独立开处理解决了加减号与乘除号的优先级问题。在这几个过程的反复调用中,始终传递fsys变量的值,保证可以在出错的情况下跳过出错的符号,使分析过程得以进行下去。
static struct ExprNode *Component();//幂运算表达式
static struct ExprNode *Atom();//原子表达式

							   //对外接口声明
extern void Parser(char *SrcFilePtr);

//语法树构造函数声明
static struct ExprNode *MakeExprNode(enum Token_Type opcode, ...);

//通过词法分析器接口GetToken获得一个记号
static void FetchToken()
{
	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)
{
	char msg[256];
	memset(msg, 0, 256);
	sprintf(msg, "Line No %5d:%s %s !", LineNo, descrip, string);

	MessageBoxA(NULL, msg, "error!", MB_OK);

	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)//语法分析器的入口
{
	if (!InitScanner(SrcFilePtr))//初始化词法分析器
	{
		printf("Open Source File Error !\n"); return;
	}
	FetchToken();//先获得一个记号
	Program();//然后进入Program递归子程序,递归下降分析
	CloseScanner();//关闭词法分析器
	return;
}

//程序Program的递归子程序
static void Program()
{
	while (token.type != NONTOKEN)//记号类型不为空
	{
		Statement();//程序有多个语句
		MatchToken(SEMICO);//直到匹配到分隔符
	}
}

//语句Statment的递归子程序
static void Statement()
{
	switch (token.type)
	{
		//罗列四种语句类型,分别分析
	case ORIGIN: OriginStatement(); break;
	case SCALE: ScaleStatement(); break;
	case ROT:  RotStatement(); break;
	case FOR: ForStatement(); break;
	default: SyntaxError(2); //否则报错
	}
}

//语句OriginStatement的递归子程序
static void OriginStatement(void)
{
	struct ExprNode *tmp;//语法树节点的类型
	MatchToken(ORIGIN);
	MatchToken(IS);
	MatchToken(L_BRACKET);

	tmp = Expression();  //Tree_trace(tmp);

	Origin_x = GetExprValue(tmp);//获取横坐标点平移距离
	DelExprTree(tmp);//删除一棵树

	MatchToken(COMMA);

	tmp = Expression();   //Tree_trace(tmp);

	Origin_y = GetExprValue(tmp);//获取纵坐标点平移距离
	DelExprTree(tmp);//删除一棵树

	MatchToken(R_BRACKET);
}

//语句ScaleStatement的递归子程序
static void ScaleStatement(void)
{
	struct ExprNode *tmp;
	MatchToken(SCALE);
	MatchToken(IS);
	MatchToken(L_BRACKET);

	tmp = Expression();  //Tree_trace(tmp);

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

	MatchToken(COMMA);

	tmp = Expression();    //Tree_trace(tmp);

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

	MatchToken(R_BRACKET);
}

//语句RotStatement的递归子程序
static void RotStatement(void)
{
	struct ExprNode *tmp;
	MatchToken(ROT);
	MatchToken(IS);

	tmp = Expression();  //Tree_trace(tmp);
	
	Rot_angle = GetExprValue(tmp);//获得旋转角度
	DelExprTree(tmp);
}

//语句ForStatement的递归子程序
//对右部文法符号的展开->遇到终结符号直接匹配,遇到非终结符就调用相应子程序
//ForStatement中唯一的非终结符是Expression,他出现在5个不同位置,分别代表循环的起始、终止、步长、横坐标、纵坐标,需要5个树节点指针保存这5棵语法树
static void ForStatement(void)
{
	//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;

	MatchToken(FOR);
	MatchToken(T);
	MatchToken(FROM);//eg:for T from

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

	Start = GetExprValue(start_ptr);//计算参数起点表达式的值
	DelExprTree(start_ptr);//释放参数起点语法树所占空间

	MatchToken(TO);

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

	End = GetExprValue(end_ptr);//计算参数终点表达式的值
	DelExprTree(end_ptr);//释放参数终点语法树所占空间

	MatchToken(STEP);

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

	Step = GetExprValue(step_ptr);//计算参数步长表达式的值
	DelExprTree(step_ptr);//释放参数步长语法树所占空间

	MatchToken(DRAW);
	MatchToken(L_BRACKET);

	x_ptr = Expression(); //Tree_trace(x_ptr);

	MatchToken(COMMA);

	y_ptr = Expression();//Tree_trace(y_ptr);

	MatchToken(R_BRACKET);

	DrawLoop(Start, End, Step, x_ptr, y_ptr); //绘制图形
	DelExprTree(x_ptr);//释放横坐标语法树所占空间
	DelExprTree(y_ptr);//释放纵坐标语法树所占空间
}

//(二元加减运算)表达式Expression的递归子程序,与上边不太相同的是,表达式需要为其构造语法树
//把函数设计为语法树节点的指针,在函数内引进2个语法树节点的指针变量,分别作为Expression左右操作数(Term)的语法树节点指针
//表达式应该是由正负号或无符号开头、由若干个项以加减号连接而成。
static struct ExprNode *Expression()//展开右部,并且构造语法树
{
	struct ExprNode *left, *right;//左右子树节点指针
	Token_Type token_tmp;//当前记号

	left = Term();//分析左操作数得到其语法树,左操作数是一个乘除运算表达式
	while (token.type == PLUS || token.type == MINUS)
	{
		token_tmp = token.type;//当前记号是加/减
		MatchToken(token_tmp);//匹配记号
		right = Term();//分析右操作数得到其语法树,右操作数是一个乘除运算表达式
		left = MakeExprNode(token_tmp, left, right);//构造运算的语法树,结果为左子树
	}
	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, *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();  //Tree_trace(tmp);
		address = MakeExprNode(FUNC, t.FuncPtr, tmp);
		MatchToken(R_BRACKET);
		break;
	case L_BRACKET:
		MatchToken(L_BRACKET);
		address = Expression(); // Tree_trace(address);
		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);//初始化va_list变量,第一个参数也就是固定参数为opcode
	switch (opcode)//根据记号的类别构造不同的节点
	{
	case CONST_ID://常数节点
		ExprPtr->Content.CaseConst = (double)va_arg(ArgPtr, double);//返回可变参数,可变参数类型是常数
		break;
	case T://参数T节点
		ExprPtr->Content.CaseParmPtr = &Parameter;//返回可变参数,可变参数类型是参数T
		break;
	case FUNC://函数调用节点
		ExprPtr->Content.CaseFunc.MathFuncPtr = (FuncPtr)va_arg(ArgPtr, FuncPtr);//可变参数类型是对应函数的指针
		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);//结束可变参数的获取

	return ExprPtr;
}

Semantic.cpp

#include "semantic.h"
#include<windef.h>
HDC hDC;
extern double
Parameter,//参数T的存储空间:记录t每次加一点的变化   在语法分析中声明的
Origin_x, Origin_y,//横纵坐标平移距离
Scale_x, Scale_y,//横纵比例因子
Rot_angle;//旋转角度

double GetExprValue(struct ExprNode * root);//获得表达式的值
void DrawPixel(unsigned long x, unsigned long y);//绘制一个点
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);//计算要绘制店的实际坐标
		DrawPixel((unsigned long)x, (unsigned long)y);//绘制这个点
	}
}

//----------计算表达式的值
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);
}

//----------绘制一个点
void DrawPixel(unsigned long x, unsigned long y)
{
	
	SetPixel(hDC, x, y, red);
}

main.cpp

#pragma warning(disable:4996)
#include "semantic.h"
#include <tchar.h> 
#define MAX_CHARS 200

extern HDC hDC;							// 窗口句柄,全局变量
char SrcFilePath[MAX_CHARS];		// 用于存放源程序文件路径
TCHAR Name[] = _T("函数绘图语言解释器");	// 窗口名

// 检查源程序文件是否合法函数声明
static bool CheckSrcFile(LPSTR);

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
TCHAR szClassName[] = _T("函数绘图语言解释器");

int WINAPI WinMain(HINSTANCE hThisInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpszArgument,
	int nFunsterStil)

{
	HWND hwnd;               /* This is the handle for our window */
	MSG messages;            /* Here messages to the application are saved */
	WNDCLASSEX wincl;        /* Data structure for the windowclass */
	int i;
	/*CTestDlg *pDlg;*/

	 /* The Window structure */
	wincl.hInstance = hThisInstance;
	wincl.lpszClassName = szClassName;
	wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
	wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
	wincl.cbSize = sizeof(WNDCLASSEX);

	/* Use default icon and mouse-pointer */
	wincl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wincl.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
	wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
	wincl.lpszMenuName = NULL;                 /* No menu */
	wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
	wincl.cbWndExtra = 0;                      /* structure or the window instance */
	/* Use Windows's default color as the background of the window */
	wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND;

	/* Register the window class, and if it fails quit the program */
	if (!RegisterClassEx(&wincl))
		return 0;

	/* The class is registered, let's create the program*/
	hwnd = CreateWindowEx(
		0,                   /* Extended possibilites for variation */
		szClassName,         /* Classname */
		_T("函数绘图语言解释器"),       /* Title Text */
		WS_OVERLAPPEDWINDOW, /* default window */
		CW_USEDEFAULT,       /* Windows decides the position */
		CW_USEDEFAULT,       /* where the window ends up on the screen */
		740,                 /* The programs width */
		490,                 /* and height in pixels */
		HWND_DESKTOP,        /* The window is a child-window to desktop */
		NULL,                /* No menu */
		hThisInstance,       /* Program Instance handler */
		NULL                 /* No Window Creation data */
	);

	/* Make the window visible on the screen */
	ShowWindow(hwnd, nFunsterStil);
	hDC = GetDC(hwnd);
	/* pDlg=new CTestDlg();
	pDlg->Create(IDD_DIALOG1,this);
	 pDlg->ShowWindow(SW_SHOW);*/

	strcpy(SrcFilePath, "test2.txt");

	if (!CheckSrcFile(SrcFilePath)) return 1;

	// --------------------------------------------
	//		调用绘图语言解释器

	Parser(SrcFilePath);
	// --------------------------------------------

	/* Run the message loop. It will run until GetMessage() returns 0 */
	while (GetMessage(&messages, NULL, 0, 0))
	{
		/* Translate virtual-key messages into character messages */
		TranslateMessage(&messages);
		/* Send message to WindowProcedure */
		DispatchMessage(&messages);
	}

	/* The program return-value is 0 - The value that PostQuitMessage() gave */
	return messages.wParam;
}


/*  This function is called by the Windows function DispatchMessage()  */

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)                  /* handle the messages */
	{
	case WM_DESTROY:
		PostQuitMessage(0);       /* send a WM_QUIT to the message queue */
		break;
	default:                      /* for messages that we don't deal with */
		return DefWindowProc(hwnd, message, wParam, lParam);
	}
	return 0;
}
// 检查源程序文件是否合法函数实现
bool CheckSrcFile(LPSTR lpszCmdParam)
{
	FILE * file = NULL;
	if (strlen(lpszCmdParam) == 0)
	{
		MessageBoxA(NULL, "未指定源程序文件 !", "错误", MB_OK);
		return false;
	}
	if ((file = fopen(lpszCmdParam, "r")) == NULL)
	{
		MessageBoxA(NULL, "打开源程序文件出错 !", "错误", MB_OK);
		MessageBoxA(NULL, lpszCmdParam, "文件名", MB_OK);
		return false;
	}
	else fclose(file);
	return true;
}

发扬开源精神方便后人,如果对你有用,hxd点点fork吧
完整项目源码


文章作者: 你的朋友
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 你的朋友 !