洛谷 P3695 CYaRon!语

去年这个时候,我还在瞻仰这道题而无从下手,转眼一年又过去了啊,时间过得好快

写解释器大概有两种动手方法:一种是程序员的思路,每次实现一点功能并调试,逐渐实现全部功能;另一种是OIer的思路,一次性码完所有代码然后开始调试。

显然第一种方法更合理一些。第二种方法虽然写起来很痛快,但是调试起来。。。

然而因为我太懒了,所以选择了第二种。

一、 规划结构

首先我们把程序过程分为“解析代码”和“执行代码”两部分。

解析代码:将源代码翻译成易于分析与执行的内部结构

执行代码:运行、维护内部代码、结构

可见,解析和执行之间,存在一个沟通的桥梁:内部结构。那么我们就先规划内部代码

二、内部结构

1、为什么要有内部结构

直接将源代码字符串完全储存下来然后运行可行吗?当然可行,但是这样做,无论是空间消耗还是时间消耗都完全不合适。将源代码翻译成内部代码(哪怕不设计新代码只是字对字的翻译),一方面压缩了空间,另一方面也便于执行。

2、代码结构

代码中存在两种基本的结构:表达式指令。指令就是如 “ihu”、“while”、“set”的这些语句,而表达式则是如 “a + 2 - b[3]” 这样的算式。显然程序是由一条一条指令拼接而成的,而表达式则是某些指令的一部分参数。不过在CYaRon!语中,还有一种特殊的结构,只存在于vars语句当中,用于声明变量。

3、变量的储存

CYaRon!语中只有两种变量:整数和整数数组。整数就用int即可,整数数组则需要写一个新的类型:

struct arr {	//数组的储存结构
	int *val, start;	//需要有数组空间和起始地址
	arr(int s, int t): start(s) { val = new int[t - s + 5]; }
	void aset(int i, int v) { val[i - start] = v; }	//赋值
	int aget(int i) { return val[i - start]; }	//取值
};

上面的代码在实现数组的同时还有两个成员函数aset与aget,意义很好理解。

与 c++ 一样,这个解释器并不会进行数组越界检查(因为我懒)。

所以我们直接把变量储存在map里,需要时直接从map里查:

map<std::string, int> inttable;	//储存整数变量
map<std::string, arr*> arrtable;	//储存数组变量

注意到我们储存数组变量实际上只存了指向数组的指针,在之后的处理中也应当注意这一点。

用map储存,不仅慢,而且在内部代码中也要处处保存着变量名的字符串。实际上存在一种更优秀也是更通用的做法,就是把变量处理成地址,之后直接在这个地址中搞事,速度快且省空间。但是因为懒,姑且先用map。

4、表达式

在传统编程语言中,复杂的表达式往往会被解析成若干个非常简单的运算。在CYaRon!语中,因为表达式只存在“+”、“-”,这无疑给了我们极大的便利:将每个表达式处理成一串“表达式块”,每个块只包含一个变量或常数以及一个符号标志,表达式的求值可以被简化成求“这些块的取值的和”。

那么
我们就可以把表达式处理成一个表达式块的链表,这个链表的实现如下:

struct Expression {	//表达式储存结构
	int type;	//表达式类型(0为常数,1为整数变量,2为数组变量)
	int symbol;	//正负
	Expression *arre;	//(数组专用)数组下标的表达式
	std::string val;	//表达式的形态(常数为数字串,变量为变量名,常数不需要带符号,数组不需要带下标)
	Expression *nxt;	//下一条表达式(我们使用链表结构储存一整个表达式)
	
	int eget() {	//表达式取值
		int num = 0;
		if(type == 0) {	//如果是常数
			for(int i = 0; i < val.size(); ++i) num = num * 10 + val[i] - '0';	//获取数值
		} else if(type == 1) {	//如果是整数变量
			num = inttable[val];	//从内存中找到变量的值
		} else if(type == 2) {	//如果是数组
			num = arrtable[val]->aget(arre->eget());	//从内存中找数组并根据下标表达式计算出下标并访问
		} else {	//其他情况
			throw("RE");	//是不存在的如果真的存在说明程序出BUG了
		}
		num *= symbol;	//带上符号
		if(nxt != NULL) return num + nxt->eget();	//如果后面还有表达式,计算后面的表达式并加上
		return num;
	}
};

在这份代码中还有一些表达式的其他属性,如表达式类型,数组下标表达式,以及表达式形态。

表达式类型描述了这个表达式(块)是常数还是整数变量还是数组,数组下标表达式专为数组服务,用来储存下标。虽然题目描述中数组下标不会嵌套,但是用这种实现方法,完全可以支持嵌套。表达式形态的意义便是储存表达式的“特征值”,实际储存的是变量名或数字串。

作为链表,理应有一个指向下一个块的指针。

这里我们还有一个有趣函数 eget,用来表达式求值,他的工作原理也相当简单,阅读代码及注释便可理解。

5、变量声明

与表达式相同的是,我们将一个vars语句中的所有变量声明处理成一个链表。

struct Initer {	//变量声明储存结构
	int type;	//变量类型(0为整数,1为数组)
	std::string name;	//变量名
	int begin, end;	//(数组专用)起始值与终止值
	Initer *nxt;	//下一条变量声明(我们使用链表结构储存一整串变量声明)
};

6、指令

嗯你没猜错,我把指令也处理成链表了。

指令有一些特殊的属性,如仅供vars使用的变量声明表,仅供if、hor、while使用的比较类型以及仅供if、hor、while使用的子指令表。

指令还包含了三个表达式,这三个表达式在不同语句中用途不同(或不使用),在注释中有详细说明。值得注意的是,在CYaRon!中,hor 的三个表达式给出的先后顺序是:循环变量、起始值、终止值,而内部指令中却把起始值放到了exp3,终止值放到了exp1,这实际上是给后面的执行创造了便利。

虽然hor语句没有显式的判断类型,但我们仍需要手动设置成“le”,这也是在为执行创造便利。

struct Instruction {	//指令的储存结构
	int type;	//指令类型(0为vars,1为set,2为yosoro,3为ihu,4为hor,5为while)
	Initer *init;	//(var专用)初始化变量列表
	Expression *exp1, *exp2, *exp3;	//exp1:set被赋值项,yosoro被输出表达式,ihu左值,hor循环变量,while左值
									//exp2:ihu右值,hor结束值,while右值
									//exp3:set源赋值表达式,hor初始值
	int judgetype;	//ihu、while、hor专用,比较类型(0为lt,1为gt,2为le,3为ge,4为eq,5为neq),hor需要始终设置为2(le)
	Instruction *subins;	//ihu、hor、while专用,子指令块
	Instruction *nxt;	//下一条指令(我们用链表结构储存一整串指令)
};

三、执行

从时间顺序来说,应该是先读取解析而后执行,但是执行部分的逻辑更为清晰,地位也更为重要,实现起来却相对简单,所以我先描述执行环节。

1、运行指令

一串switch而已,见代码:

void Run(Instruction *ins)	//处理指令
{
	while(ins != NULL) //一条一条执行下去下去
	{
		switch(ins->type)	//分清指令类型
		{
		case 0:
			_vars(ins);
			break;
		case 1:
			_set(ins);
			break;
		case 2:
			_yosoro(ins);
			break;
		case 3:
			_ihu(ins);
			break;
		case 4:
			_hor(ins);
			break;
		case 5:
			_while(ins);
			break;
		default:
			throw("RE: Something wrong with the type of a instruction.");
		}
		ins = ins->nxt;	//下一条
	}
}

虽然此题保证不会出现错误代码,不过顺便把错误判断写上也不碍事,而且思路更为清晰。之后你会看到此解释器中有无数个CE判断。

分别实现每一种语句:

vars:

void _vars(Instruction *ins)	//处理var语句
{
	for(Initer *i = ins->init; i != NULL; i = i->nxt) //遍历每一条变量声明
	{
		if(i->type == 0) inttable[i->name] = 0;	//如果是整数,直接在整数内存中设置为0
		else if(i->type == 1) {	//如果是数组
			arrtable[i->name] = new arr(i->begin, i->end);	//在数组内存中插入一个新的数组
		} else {	//其他情况
			throw("RE: Something wrong with the type of a Initer.");	//不可能有其他情况如果有就说明程序出BUG了
		}
	}
}

set:

void _set(Instruction *ins)	//处理set语句
{
	Expression *exp1 = ins->exp1, *exp3 = ins->exp3;	//为了方便操作把链表换个名字(因为是指针所以指向的结构是不会变的)
	if(exp1->type == 1) {	//如果被赋值项是整数变量
		inttable[exp1->val] = exp3->eget();	//直接在内存中改变值
	} else if(exp1->type == 2) {	//如果被赋值项是数组
		arrtable[exp1->val]->aset(exp1->arre->eget(), exp3->eget());	//获取数组下标并根据下标设置数组的值
	} else {	//其他情况
		throw("CE: exp1 is not a variable");	//不可能有其他情况,如果有可能是输入时表达式就不正确
	}
}

yosoro:

void _yosoro(Instruction *ins)	//处理yosoro语句
{
	printf("%d ", ins->exp1->eget());	//计算表达式的值并输出,后跟一个空格
}

ihu:

bool _ihu(Instruction *ins)	//处理ihu语句
{
	Expression *exp1 = ins->exp1, *exp2 = ins->exp2;	//为了方便操作把链表换个名字(因为是指针所以指向的结构是不会变的)
	switch(ins->judgetype)	//对于每一种判断,如果不符合就返回false
	{
	case 0:	//lt
		if(exp1->eget() >= exp2->eget()) return false;
		break;
	case 1:	//gt
		if(exp1->eget() <= exp2->eget()) return false;
		break;
	case 2:	//le
		if(exp1->eget() > exp2->eget()) return false;
		break;
	case 3:	//ge
		if(exp1->eget() < exp2->eget()) return false;
		break;
	case 4:	//eq
		if(exp1->eget() != exp2->eget()) return false;
		break;
	case 5: //neq
		if(exp1->eget() == exp2->eget()) return false;
		break;
	default:
		throw("RE: Something wrong with the type of a judge");
	}
	Run(ins->subins);	//如果符合结果就执行子指令块
	return true;	//返回true
}

ihu的实现比较特殊,他要执行子指令,同时还有一个返回值表示判断结果。

hor:

void _hor(Instruction *ins)	//处理hor语句
{
	Expression *exp1 = ins->exp1, *exp2 = ins->exp2, *exp3 = ins->exp3;	//为了方便操作把链表换个名字(因为是指针所以指向的结构是不会变的)
	_set(ins);	//把exp3赋值给exp1
	while(_ihu(ins)) {	//利用ihu语句反复执行
		if(exp1->type == 1) {	//+1操作,循环变量如果是整数变量
			++inttable[exp1->val];	//直接加
		} else if(exp1->type == 2) {	//如果是数组
			int i = exp1->arre->eget();	//获得下标
			arrtable[exp1->val]->aset(i, arrtable[exp1->val]->aget(i) + 1); //加上去
		} else {	//如果其他类型
			throw("CE: exp1 is not a variable");	//不可能
		}
	}
}

这里我们就可以看出之前把hor的初始值放到exp3而终止值放到exp2的用意了,我们利用_ihu不停的判断兼执行,而ihu判断两个表达式的则是exp1与exp2,初始值在赋值完成后就没用了,所以放到了exp3。

while:

void _while(Instruction *ins)	//处理while语句
{
	while(_ihu(ins));	//利用ihu反复执行
}

while实现起来最简单了。

执行部分到这里就没有了,接下来就要写最麻烦的读入解析部分了。

四、读入

读入没什么可简介的。

1、读入方式

有人喜欢一次读入整个代码文件然后在字符数组里搞事,有人喜欢一行一行的读,有人是一个单词一个单词的读入,但是鉴于本人太过于懒惰不愿意写那么复杂的读入,所以就用getchar了,边读入边处理。每次需要一个字符就读一个字符,读到EOF就算是读完了。

但是还不想直接用getchar,虽然题目中说代码里没有注释,但是由于觉得难受还是加上了注释判断,注释用 “#”开头,此后一直到行末都是注释内容。

我们先写一个可以吞掉注释getchar:

char _getc() { //一个可以吞掉注释的getchar()
	char ch = getchar();
	if(ch == '#') while(ch != '\n' && ch != EOF) ch = getchar();
	return ch; 
}

我们弄一个全局都在使用的字符变量 “c”,以后每次都把字符读进这里边来:

char c;

// 以后就这么读入
c = _getc();

2、“c” 中到底有什么

我们总是一个字符一个字符的读入,然后处理即可。但是注意到一个问题:当我们读入一个内容的时候,是如何知道它读完了呢?其实有两种情况:

1、当前内容只使用特定的字符,读到别的字符就意味着结束了。例如读入常数时,如果读到不是数字的字符就意味着常数读完了;读入变量名时,遇到不是字母的字符意味着变量名读完了。

2、当前内容的字符比较多,但是有一个特殊的字符,我们称之为结束标志,一旦读到结束标志就意味着读完了,没遇到结束标志就一直读下去。如读入数组下标表达式时,读到 “ ] ” 就可以知道下标表达式读完了。

这两种结束方式的区别在于,第一种方法,在结束读入后,c 中保存的是下一个要读入的内容的第一个字符,也就意味着读入下一个内容时不必先读入而默认 c 中有内容,对于后一种情况则不然。

可是读入时怎么能知道 c 中到底保存了结束符还是自己的第一个字符?这个判断自己是往往做不了的。虽然我不知道上一个读入是怎么结束的,但是我可以知道自己是怎么结束的。那么我们规定:以结束标志结束的读入要在结束后再读入一次,这样就可以保证对于每一次读入,c 中预先总是保存了他的第一个字符而无须读入了

3、读入指令

void ReadInstruction(char endc, Instruction *ins)
{
	while(c != endc) 
	{
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //读到不是空字符为止
		if(c == '{' || c == ':')	//指令
		{
			std::string opt;
			c = _getc();
			while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //读到不是空字符为止
			while(c >= 'a' && c <= 'z') { //获取指令名称
				opt.push_back(c);
				c = _getc();
			}
			if(c != ' ' && c != '\t' && c != '\n' && c != '\r') throw("CE: Unexpected character. ");	//不是以空字符结尾的直接CE
			
			if(opt == "vars")
				readvars(ins);
			else if(opt == "set")
				readset(ins);
			else if(opt == "yosoro")
				readyosoro(ins);
			else if(opt == "ihu")
				readihu(ins);
			else if(opt == "hor")
				readhor(ins);
			else if(opt == "while")
				readwhile(ins);
			else	//未定义的指令
				throw("CE: Unknow Instruciton. ");
				
			ins->nxt = new Instruction();
			ins = ins->nxt;
			
			while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //读到不是空字符为止
		} else {	//非法字符
			throw("CE: Unexpected character. ");
		}
	}
}

两个参数分别为结束标志与要读入指令的指针所在。

结束标志有主程序的 EOF,也有子指令的 “ } ”,这个要看调用者的意思。

指令所在指针必须事先初始化

其他地方比较好理解。

读入一些非指令内容

读入变量声明:

void readiniter(Initer *init)	//读取变量声明
{
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//吞掉空字符
	if(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) throw("CE: Unexpected character. "); //非字母不可以作变量名
	std::string name;
	while((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {	//读取变量名
		name.push_back(c);
		c = _getc();
	}
	init->name = name;
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //允许变量名和冒号之间有空字符
	if(c != ':') throw("CE");	//非法字符
	c = _getc();
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//允许冒号和类型之间有空字符
	name.clear();
	if(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) throw("CE: Unexpected character. TT"); //非字母不可以作类型名
	while((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
		name.push_back(c);
		c = _getc();
	}
	if(name == "int") {	//int
		init->type = 0;
	} else if(name == "array") {	//array
		init->type = 1;
		if(c != '[') throw("CE: Unexpected character. ");	//array后面紧跟的不是"["就不行(我规定的)
		name.clear();
		c = _getc();
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//允许方括号后面有空字符
		while((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {	//读取类型名
			name.push_back(c);
			c = _getc();
		}
		if(name != "int") throw("CE: Unknow type. ");	//不是int肯定不行
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//允许类型名后面有空字符
		if(c != ',') throw("CE: Unexpected character. ");	//不是","也不行
		c = _getc();
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();
		int num = 0;
		while(c >= '0' && c <= '9') {	//读取起始下标
			num = num * 10 + c - '0';
			c = _getc();
		}
		init->begin = num;
		if(c == '.') {	//".."必须与两个值紧凑的挨在一起(我规定的)
			c = _getc();
			if(c != '.') throw("CE: Unexpected character. ");
		} else throw("CE");
		c = _getc();
		num = 0;
		while(c >= '0' && c <= '9') { //读取终止下标
			num = num * 10 + c - '0';
			c = _getc();
		}
		init->end = num;
		c = _getc();
	} else throw("CE: Unknow type. ");
}

变量声明的参数也是指向该声明的指针,也必须初始化好。

读取表达式:

void readexpression(char endc, Expression *&expr)	//读取表达式,endc为表达式终止的标志
{
	expr = new Expression();
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //先把前面的空字符吞了
	if(c == '-') {	//如果是"-",那就是负的
		expr->symbol = -1; 
		c = _getc(); while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//因为读到了符号,所以主体还在后面
	}
	else if(c == '+') {	//如果是"+",那就是正的
		expr->symbol = 1;
		c = _getc(); while(c == ' ' || c == '\n' || c == '\t' || c == '\r')	c = _getc();	//理由同上
	} else {
		expr->symbol = 1;	//没指明正负也是正的,只是不用继续往下读了
	}
	
	if(c >= '0' && c <= '9') {	//如果是常数
		expr->type = 0;
		std::string num;	//直接把数字读成字符串,解析扔给执行那边,读入这里就不管了
		while(c >= '0' && c <= '9') {
			num.push_back(c);
			c = _getc();
		}
		expr->val = num;
	} else if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {	//如果是变量
		std::string name;
		while((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {	//读完变量名
			name.push_back(c);
			c = _getc();
		}
		expr->val = name;
		if(c == '[') {	//如果是array
			expr->type = 2;
			c = _getc();
			readexpression(']', expr->arre);	//以"]"为终止标志读取数组下标表达式
		} else expr->type = 1;
	}
	while(c != endc && (c == ' ' || c == '\n' || c == '\t' || c == '\r')) c = _getc();	//一直吞空字符,直到读到结束标志或其他字符为止(这里结束标志也可能是四大空字符之一)
	if(c == '+' || c == '-') readexpression(endc, expr->nxt);	//如果是正负(加减)号就读下一个表达式
	else if(c == endc) c = _getc();	//终止符就再读一个字符走人
	else throw("CE: Unexpected character. ");	//不可能是别的字符,如果是就CE
}

表达式也有结束标志,例如逗号,换行,“]”,这些要看调用者的意愿。

读入表达式传入的是指向表达式的指针的引用,读入表达式时会自行初始化,无须提前初始化。

读入比较判断:

int readjudge()
{
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();
	if(c == 'l') {	//读取比教的类型,不符合6个类型之一的CE掉
		c = _getc();
		if(c == 't') {
			return 0;	//lt
		} else if(c == 'e') {
			return 2;	//le
		} else throw("CE: Unexpected character. ");
	} else if(c == 'g') {
		c = _getc();
		if(c == 't') {
			return 1;	//gt
		} else if(c == 'e') {
			return 3;	//ge
		} else throw("CE: Unexpected character. ");
	} else if(c == 'e') {
		c = _getc();
		if(c == 'q') {
			return 4;	//eq
		} else throw("CE: Unexpected character. ");
	} else if(c == 'n') {
		if(c == 'e') {
			c = _getc();
			if(c == 'q') {
				return 5;	//neq
			} else throw("CE: Unexpected character. ");
		} else throw("CE: Unexpected character. ");
	} else throw("CE: Unexpected character. ");
}

这个没什么可说的。

readvars:

void readvars(Instruction *ins)	//读取vars
{
	ins->init = new Initer();
	Initer *init = ins->init;
	while(c != '}') {
		readiniter(init);
		init->nxt = new Initer();
		init = init->nxt;
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();
	}
	ins->type = 0;
	c = _getc();
}

先给指令的变量声明表初始化好。每次读取完换成下一条接着读取。

readset:

void readset(Instruction *ins)	//读取set
{
	ins->type = 1;
	readexpression(',', ins->exp1);	//以逗号为结束标志读取exp1
	readexpression('\n', ins->exp3); //以换行为结束标志读取exp2
}

readyosoro:

void readyosoro(Instruction *ins)	//读取yosoro
{
	ins->type = 2;
	readexpression('\n', ins->exp1);	//以换行为结束标志读取exp1
}

readihu:

void readihu(Instruction *ins)	//读取ihu
{
	ins->type = 3;
	ins->judgetype = readjudge();	//读取比较类型
	readexpression(',', ins->exp1);	//以逗号为结束标志读取exp1
	readexpression('\n', ins->exp2);	//以换行为结束标志读取exp2
	ins->subins = new Instruction();
	ReadInstruction('}', ins->subins);	//以有括号为结束标志读取subins
}

从 ihu 开始出现了子指令的读入。

readhor:

void readhor(Instruction *ins)	//读取hor
{
	ins->type = 4;
	ins->judgetype = 2;	//比较类型天然为le
	readexpression(',', ins->exp1);	//以=为结束标志读取循环变量
	readexpression(',', ins->exp3);	//以t为结束标志读取初值
	readexpression('\n', ins->exp2);	//以换行为结束标志读取终止值
	ins->subins = new Instruction();
	ReadInstruction('}', ins->subins);
}

readwhile:

void readwhile(Instruction *ins)	//读取while
{
	readihu(ins);	//因为内部是相同的直接很赖皮的用ihu读入
	ins->type = 5;	//然后把类型改过来就行了
}

while 和 ihu 在表示上实在是太像了。

至此读入部分也完成了。

五、收尾

Instruction *Main;	//主指令串

int main()
{
	c = _getc();	//先读一个字符
	Main = new Instruction();
	try {
		ReadInstruction(EOF, Main);	//以EOF为结束标志读取Main
		Run(Main);
        printf("\n");
	} catch (const char* e) {
		cerr << e << endl;
	}
	return 0;
}

在提交的时候为了保证效率可以把异常处理删掉。

六、测试

写是写完了,正确性还得不到保证。我们写一些CYaRon!程序测试一下:

test1.cyr 三连击直接输出:

既然题目描述中第一个测试点是三连击直接输出。

# test1.cyr

:yosoro 192
:yosoro 384
:yosoro 576
:yosoro 219
:yosoro 438
:yosoro 657
:yosoro 273
:yosoro 546
:yosoro 819
:yosoro 327
:yosoro 654
:yosoro 981

输出结果:

192 384 576 219 438 657 273 546 819 327 654 981 

通过。

test2.cyr A+B Problem:

# test2.cyr

{ vars
	a:int
	b:int
}

:set a, 1
:set b, 2

:yosoro a + b

输出结果:

3

通过。

test3.cyr 把三连击放入数组

题目描述不说就自己编一个。

与 test1 不同,test3 先把三连击的答案存入数组,而后输出。

# test3.cyr

{ vars 
	myarr:array[int, 100..150]
}

:set myarr[100], 192
:set myarr[101], 384
:set myarr[102], 576
:set myarr[103], 219
:set myarr[104], 438
:set myarr[105], 657
:set myarr[106], 273
:set myarr[107], 546
:set myarr[108], 819
:set myarr[109], 327
:set myarr[110], 654
:set myarr[111], 981

:yosoro myarr[100]
:yosoro myarr[101]
:yosoro myarr[102]
:yosoro myarr[103]
:yosoro myarr[104]
:yosoro myarr[105]
:yosoro myarr[106]
:yosoro myarr[107]
:yosoro myarr[108]
:yosoro myarr[109]
:yosoro myarr[110]
:yosoro myarr[111]

输出结果:

192 384 576 219 438 657 273 546 819 327 654 981

通过。

test4.cyr 数组翻转

再自己编一个程序。

# test4.cyr

{ vars
	myarr:array[int, 1..6]
	tmp:int
}

:set myarr[1], 1
:set myarr[2], 2
:set myarr[3], 3
:set myarr[4], 4
:set myarr[5], 5
:set myarr[6], 6

:set tmp, myarr[1]
:set myarr[1], myarr[6]
:set myarr[6], tmp

:set tmp, myarr[2]
:set myarr[2], myarr[5]
:set myarr[5], tmp

:set tmp, myarr[3]
:set myarr[3], myarr[4]
:set myarr[4], tmp

:yosoro myarr[1]
:yosoro myarr[2]
:yosoro myarr[3]
:yosoro myarr[4]
:yosoro myarr[5]
:yosoro myarr[6]

输出结果:

6 5 4 3 2 1

通过。

test5.cyr - test10.cyr

因为过于复杂并且我想要睡觉以及明天月考还有现在晚上12:30等种种原因实在懒得写了。

不过先把没测试过的语句测试一遍。

testihu.cyr

# testihu.cyr

{ vars
	a:int
	b:array[int, 1..1]
}

:set a, 233
:set b[1], 233

{ ihu eq, a, b[1] # 这里其他判断类型都试一下
	:yosoro 1
}

输出结果:

1

通过。

testhor.cyr

# testhor.cyr

{ vars 
	myarr:array[int, 100..150]
	i:int
}

:set myarr[100], 192
:set myarr[101], 384
:set myarr[102], 576
:set myarr[103], 219
:set myarr[104], 438
:set myarr[105], 657
:set myarr[106], 273
:set myarr[107], 546
:set myarr[108], 819
:set myarr[109], 327
:set myarr[110], 654
:set myarr[111], 981

{ hor i = 100 to 111
	:yosoro myarr[i]
}

输出结果:

192 384 576 219 438 657 273 546 819 327 654 981 

通过。

testwhile.cyr

# testwhile.cyr

{ vars 
	myarr:array[int, 100..150]
	i:int
}

:set myarr[100], 192
:set myarr[101], 384
:set myarr[102], 576
:set myarr[103], 219
:set myarr[104], 438
:set myarr[105], 657
:set myarr[106], 273
:set myarr[107], 546
:set myarr[108], 819
:set myarr[109], 327
:set myarr[110], 654
:set myarr[111], 981

:set i, 111

{ while ge, i, 100
	:yosoro myarr[i]
	:set i, i - 1
}

输出结果:

981 654 327 819 546 273 657 438 219 576 384 192

通过。

七、结束了

到现在CYaRon!语解释器已经写完了,该测试的也测试过了,可以交了。不过,这个程序有两个已知BUG:

1、set语句被赋值表达式可以是很长一串,因为我们只判断处理了第一项,虽然不会引发错误,但还是不合理的

2、题目保证最后一行一定是空行,而此解释器也必须保证这一点否则会出错,虽然不影响评测,但还是不完美

如果有其他BUG的存在,请务必指出!感激不尽

在最后贴一下完整代码吧:

#include<bits/stdc++.h>

using namespace std;

char _getc() { //一个可以吞掉注释的getchar()
	char ch = getchar();
	if(ch == '#') while(ch != '\n' && ch != EOF) ch = getchar();
	return ch; 
}

struct arr {	//数组的储存结构
	int *val, start;	//需要有数组空间和起始地址
	arr(int s, int t): start(s) { val = new int[t - s + 5]; }
	void aset(int i, int v) { val[i - start] = v; }	//赋值
	int aget(int i) { return val[i - start]; }	//取值
};

map<std::string, int> inttable;	//储存整数变量
map<std::string, arr*> arrtable;	//储存数组变量

struct Initer {	//变量声明储存结构
	int type;	//变量类型(0为整数,1为数组)
	std::string name;	//变量名
	int begin, end;	//(数组专用)起始值与终止值
	Initer *nxt;	//下一条变量声明(我们使用链表结构储存一整串变量声明)
};

struct Expression {	//表达式储存结构
	int type;	//表达式类型(0为常数,1为整数变量,2为数组变量)
	int symbol;	//正负
	Expression *arre;	//(数组专用)数组下标的表达式
	std::string val;	//表达式的形态(常数为数字串,变量为变量名,常数不需要带符号,数组不需要带下标)
	Expression *nxt;	//下一条表达式(我们使用链表结构储存一整个表达式)
	
	int eget() {	//表达式取值
		int num = 0;
		if(type == 0) {	//如果是常数
			for(int i = 0; i < val.size(); ++i) num = num * 10 + val[i] - '0';	//获取数值
		} else if(type == 1) {	//如果是整数变量
			num = inttable[val];	//从内存中找到变量的值
		} else if(type == 2) {	//如果是数组
			num = arrtable[val]->aget(arre->eget());	//从内存中找数组并根据下标表达式计算出下标并访问
		} else {	//其他情况
			throw("RE");	//是不存在的如果真的存在说明程序出BUG了
		}
		num *= symbol;	//带上符号
		if(nxt != NULL) return num + nxt->eget();	//如果后面还有表达式,计算后面的表达式并加上
		return num;
	}
};

struct Instruction {	//指令的储存结构
	int type;	//指令类型(0为var,1为set,2为yosoro,3为ihu,4为hor,5为while)
	Initer *init;	//(var专用)初始化变量列表
	Expression *exp1, *exp2, *exp3;	//exp1:set被赋值项,yosoro被输出表达式,ihu左值,hor循环变量,while左值
									//exp2:ihu右值,hor结束值,while右值
									//exp3:set源赋值表达式,hor初始值
	int judgetype;	//ihu、while、hor专用,比较类型(0为lt,1为gt,2为le,3为ge,4为eq,5为neq),hor需要始终设置为2(le)
	Instruction *subins;	//ihu、hor、while专用,子指令块
	Instruction *nxt;	//下一条指令(我们用链表结构储存一整串指令)
};

void Run(Instruction *ins);

void _vars(Instruction *ins)	//处理var语句
{
	for(Initer *i = ins->init; i != NULL; i = i->nxt) //遍历每一条变量声明
	{
		if(i->type == 0) inttable[i->name] = 0;	//如果是整数,直接在整数内存中设置为0
		else if(i->type == 1) {	//如果是数组
			arrtable[i->name] = new arr(i->begin, i->end);	//在数组内存中插入一个新的数组
		} else {	//其他情况
			throw("RE: Something wrong with the type of a Initer.");	//不可能有其他情况如果有就说明程序出BUG了
		}
	}
}

void _set(Instruction *ins)	//处理set语句
{
	Expression *exp1 = ins->exp1, *exp3 = ins->exp3;	//为了方便操作把链表换个名字(因为是指针所以指向的结构是不会变的)
	if(exp1->type == 1) {	//如果被赋值项是整数变量
		inttable[exp1->val] = exp3->eget();	//直接在内存中改变值
	} else if(exp1->type == 2) {	//如果被赋值项是数组
		arrtable[exp1->val]->aset(exp1->arre->eget(), exp3->eget());	//获取数组下标并根据下标设置数组的值
	} else {	//其他情况
		throw("CE: exp1 is not a variable");	//不可能有其他情况,如果有可能是输入时表达式就不正确
	}
}

void _yosoro(Instruction *ins)	//处理yosoro语句
{
	printf("%d ", ins->exp1->eget());	//计算表达式的值并输出,后跟一个空格
}

bool _ihu(Instruction *ins)	//处理ihu语句
{
	Expression *exp1 = ins->exp1, *exp2 = ins->exp2;	//为了方便操作把链表换个名字(因为是指针所以指向的结构是不会变的)
	switch(ins->judgetype)	//对于每一种判断,如果不符合就返回false
	{
	case 0:	//lt
		if(exp1->eget() >= exp2->eget()) return false;
		break;
	case 1:	//gt
		if(exp1->eget() <= exp2->eget()) return false;
		break;
	case 2:	//le
		if(exp1->eget() > exp2->eget()) return false;
		break;
	case 3:	//ge
		if(exp1->eget() < exp2->eget()) return false;
		break;
	case 4:	//eq
		if(exp1->eget() != exp2->eget()) return false;
		break;
	case 5: //neq
		if(exp1->eget() == exp2->eget()) return false;
		break;
	default:
		throw("RE: Something wrong with the type of a judge");
	}
	Run(ins->subins);	//如果符合结果就执行子指令块
	return true;	//返回true
}

void _hor(Instruction *ins)	//处理hor语句
{
	Expression *exp1 = ins->exp1, *exp2 = ins->exp2, *exp3 = ins->exp3;	//为了方便操作把链表换个名字(因为是指针所以指向的结构是不会变的)
	_set(ins);	//把exp3赋值给exp1
	while(_ihu(ins)) {	//利用ihu语句反复执行
		if(exp1->type == 1) {	//+1操作,循环变量如果是整数变量
			++inttable[exp1->val];	//直接加
		} else if(exp1->type == 2) {	//如果是数组
			int i = exp1->arre->eget();	//获得下标
			arrtable[exp1->val]->aset(i, arrtable[exp1->val]->aget(i) + 1); //加上去
		} else {	//如果其他类型
			throw("CE: exp1 is not a variable");	//不可能
		}
	}
}

void _while(Instruction *ins)	//处理while语句
{
	while(_ihu(ins));	//利用ihu反复执行
}

void Run(Instruction *ins)	//处理指令
{
	while(ins != NULL) //一条一条执行下去下去
	{
		switch(ins->type)	//分清指令类型
		{
		case 0:
			_vars(ins);
			break;
		case 1:
			_set(ins);
			break;
		case 2:
			_yosoro(ins);
			break;
		case 3:
			_ihu(ins);
			break;
		case 4:
			_hor(ins);
			break;
		case 5:
			_while(ins);
			break;
		default:
			throw("RE: Something wrong with the type of a instruction.");
		}
		ins = ins->nxt;	//下一条
	}
}

Instruction *Main;	//主指令串
char c;	//用来保存当前字符

void ReadInstruction(char endc, Instruction *ins);

void readiniter(Initer *init)	//读取变量声明
{
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//吞掉空字符
	if(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) throw("CE: Unexpected character. "); //非字母不可以作变量名
	std::string name;
	while((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {	//读取变量名
		name.push_back(c);
		c = _getc();
	}
	init->name = name;
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //允许变量名和冒号之间有空字符
	if(c != ':') throw("CE");	//非法字符
	c = _getc();
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//允许冒号和类型之间有空字符
	name.clear();
	if(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) throw("CE: Unexpected character. TT"); //非字母不可以作类型名
	while((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
		name.push_back(c);
		c = _getc();
	}
	if(name == "int") {	//int
		init->type = 0;
	} else if(name == "array") {	//array
		init->type = 1;
		if(c != '[') throw("CE: Unexpected character. ");	//array后面紧跟的不是"["就不行(我规定的)
		name.clear();
		c = _getc();
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//允许方括号后面有空字符
		while((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {	//读取类型名
			name.push_back(c);
			c = _getc();
		}
		if(name != "int") throw("CE: Unknow type. ");	//不是int肯定不行
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//允许类型名后面有空字符
		if(c != ',') throw("CE: Unexpected character. ");	//不是","也不行
		c = _getc();
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();
		int num = 0;
		while(c >= '0' && c <= '9') {	//读取起始下标
			num = num * 10 + c - '0';
			c = _getc();
		}
		init->begin = num;
		if(c == '.') {	//".."必须与两个值紧凑的挨在一起(我规定的)
			c = _getc();
			if(c != '.') throw("CE: Unexpected character. ");
		} else throw("CE");
		c = _getc();
		num = 0;
		while(c >= '0' && c <= '9') { //读取终止下标
			num = num * 10 + c - '0';
			c = _getc();
		}
		init->end = num;
		c = _getc();
	} else throw("CE: Unknow type. ");
}

void readexpression(char endc, Expression *&expr)	//读取表达式,endc为表达式终止的标志
{
	expr = new Expression();
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //先把前面的空字符吞了
	if(c == '-') {	//如果是"-",那就是负的
		expr->symbol = -1; 
		c = _getc(); while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();	//因为读到了符号,所以主体还在后面
	}
	else if(c == '+') {	//如果是"+",那就是正的
		expr->symbol = 1;
		c = _getc(); while(c == ' ' || c == '\n' || c == '\t' || c == '\r')	c = _getc();	//理由同上
	} else {
		expr->symbol = 1;	//没指明正负也是正的,只是不用继续往下读了
	}
	
	if(c >= '0' && c <= '9') {	//如果是常数
		expr->type = 0;
		std::string num;	//直接把数字读成字符串,解析扔给执行那边,读入这里就不管了
		while(c >= '0' && c <= '9') {
			num.push_back(c);
			c = _getc();
		}
		expr->val = num;
	} else if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {	//如果是变量
		std::string name;
		while((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {	//读完变量名
			name.push_back(c);
			c = _getc();
		}
		expr->val = name;
		if(c == '[') {	//如果是array
			expr->type = 2;
			c = _getc();
			readexpression(']', expr->arre);	//以"]"为终止标志读取数组下标表达式
		} else expr->type = 1;
	}
	while(c != endc && (c == ' ' || c == '\n' || c == '\t' || c == '\r')) c = _getc();	//一直吞空字符,直到读到结束标志或其他字符为止(这里结束标志也可能是四大空字符之一)
	if(c == '+' || c == '-') readexpression(endc, expr->nxt);	//如果是正负(加减)号就读下一个表达式
	else if(c == endc) c = _getc();	//终止符就再读一个字符走人
	else throw("CE: Unexpected character. ");	//不可能是别的字符,如果是就CE
}

int readjudge()
{
	while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();
	if(c == 'l') {	//读取比教的类型,不符合6个类型之一的CE掉
		c = _getc();
		if(c == 't') {
			c = _getc();
			return 0;	//lt
		} else if(c == 'e') {
			c = _getc();
			return 2;	//le
		} else throw("CE: Unexpected character. ");
	} else if(c == 'g') {
		c = _getc();
		if(c == 't') {
			c = _getc();
			return 1;	//gt
		} else if(c == 'e') {
			c = _getc();
			return 3;	//ge
		} else throw("CE: Unexpected character. ");
	} else if(c == 'e') {
		c = _getc();
		if(c == 'q') {
			c = _getc();
			return 4;	//eq
		} else throw("CE: Unexpected character. ");
	} else if(c == 'n') {
		c = _getc();
		if(c == 'e') {
			c = _getc();
			if(c == 'q') {
				c = _getc();
				return 5;	//neq
			} else throw("CE: Unexpected character. ");
		} else throw("CE: Unexpected character. ");
	} else throw("CE: Unexpected character. ");
	c = _getc();
}

void readvars(Instruction *ins)	//读取vars
{
	ins->init = new Initer();
	Initer *init = ins->init;
	while(c != '}') {
		readiniter(init);
		init->nxt = new Initer();
		init = init->nxt;
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc();
	}
	ins->type = 0;
	c = _getc();
}

void readset(Instruction *ins)	//读取set
{
	ins->type = 1;
	readexpression(',', ins->exp1);	//以逗号为结束标志读取exp1
	readexpression('\n', ins->exp3); //以换行为结束标志读取exp2
}

void readyosoro(Instruction *ins)	//读取yosoro
{
	ins->type = 2;
	readexpression('\n', ins->exp1);	//以换行为结束标志读取exp1
}

void readihu(Instruction *ins)	//读取ihu
{
	ins->type = 3;
	ins->judgetype = readjudge();	//读取比较类型
	c = _getc();
	readexpression(',', ins->exp1);	//以逗号为结束标志读取exp1
	readexpression('\n', ins->exp2);	//以换行为结束标志读取exp2
	ins->subins = new Instruction();
	ReadInstruction('}', ins->subins);	//以有括号为结束标志读取subins 
}

void readhor(Instruction *ins)	//读取hor
{
	ins->type = 4;
	ins->judgetype = 2;	//比较类型天然为le
	readexpression(',', ins->exp1);	//以=为结束标志读取循环变量
	readexpression(',', ins->exp3);	//以t为结束标志读取初值
	readexpression('\n', ins->exp2);	//以换行为结束标志读取终止值
	ins->subins = new Instruction();
	ReadInstruction('}', ins->subins);
}

void readwhile(Instruction *ins)	//读取while
{
	readihu(ins);	//因为内部是相同的直接很赖皮的用ihu读入
	ins->type = 5;	//然后把类型改过来就行了
}

void ReadInstruction(char endc, Instruction *ins)
{
	while(c != endc) 
	{
		while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //读到不是空字符为止
		if(c == '{' || c == ':')	//指令
		{
			std::string opt;
			c = _getc();
			while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //读到不是空字符为止
			while(c >= 'a' && c <= 'z') { //获取指令名称
				opt.push_back(c);
				c = _getc();
			}
			if(c != ' ' && c != '\t' && c != '\n' && c != '\r') throw("CE: Unexpected character. ");	//不是以空字符结尾的直接CE
			
			if(opt == "vars")
				readvars(ins);
			else if(opt == "set")
				readset(ins);
			else if(opt == "yosoro")
				readyosoro(ins);
			else if(opt == "ihu")
				readihu(ins);
			else if(opt == "hor")
				readhor(ins);
			else if(opt == "while")
				readwhile(ins);
			else	//未定义的指令
				throw("CE: Unknow Instruciton. ");
				
			ins->nxt = new Instruction();
			ins = ins->nxt;
			
			while(c == ' ' || c == '\n' || c == '\t' || c == '\r') c = _getc(); //读到不是空字符为止
		} else {	//非法字符
			throw("CE: Unexpected character. ");
		}
	}
	c = _getc();
}

int main()
{
	c = _getc();	//先读一个字符
	Main = new Instruction();
	try {
		ReadInstruction(EOF, Main);	//以EOF为结束标志读取Main
		Run(Main);
		printf("\n");
	} catch (const char* e) {
		cerr << e << endl;
	}
	return 0;
}
posted @ 2018-10-12 00:39  cjrsacred  阅读(219)  评论(0编辑  收藏  举报