• 洛谷 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 != '
    ' && 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 == '
    ' || c == '	' || c == '
    ') c = _getc(); //读到不是空字符为止
    		if(c == '{' || c == ':')	//指令
    		{
    			std::string opt;
    			c = _getc();
    			while(c == ' ' || c == '
    ' || c == '	' || c == '
    ') c = _getc(); //读到不是空字符为止
    			while(c >= 'a' && c <= 'z') { //获取指令名称
    				opt.push_back(c);
    				c = _getc();
    			}
    			if(c != ' ' && c != '	' && c != '
    ' && c != '
    ') 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 == '
    ' || c == '	' || c == '
    ') c = _getc(); //读到不是空字符为止
    		} else {	//非法字符
    			throw("CE: Unexpected character. ");
    		}
    	}
    }
    

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

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

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

    其他地方比较好理解。

    读入一些非指令内容

    读入变量声明:

    void readiniter(Initer *init)	//读取变量声明
    {
    	while(c == ' ' || c == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') c = _getc(); //允许变量名和冒号之间有空字符
    	if(c != ':') throw("CE");	//非法字符
    	c = _getc();
    	while(c == ' ' || c == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') c = _getc();	//允许类型名后面有空字符
    		if(c != ',') throw("CE: Unexpected character. ");	//不是","也不行
    		c = _getc();
    		while(c == ' ' || c == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') c = _getc(); //先把前面的空字符吞了
    	if(c == '-') {	//如果是"-",那就是负的
    		expr->symbol = -1; 
    		c = _getc(); while(c == ' ' || c == '
    ' || c == '	' || c == '
    ') c = _getc();	//因为读到了符号,所以主体还在后面
    	}
    	else if(c == '+') {	//如果是"+",那就是正的
    		expr->symbol = 1;
    		c = _getc(); while(c == ' ' || c == '
    ' || c == '	' || c == '
    ')	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 == '
    ' || c == '	' || c == '
    ')) 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 == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') c = _getc();
    	}
    	ins->type = 0;
    	c = _getc();
    }
    

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

    readset:

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

    readyosoro:

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

    readihu:

    void readihu(Instruction *ins)	//读取ihu
    {
    	ins->type = 3;
    	ins->judgetype = readjudge();	//读取比较类型
    	readexpression(',', ins->exp1);	//以逗号为结束标志读取exp1
    	readexpression('
    ', 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('
    ', 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("
    ");
    	} 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 != '
    ' && 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 == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') c = _getc(); //允许变量名和冒号之间有空字符
    	if(c != ':') throw("CE");	//非法字符
    	c = _getc();
    	while(c == ' ' || c == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') c = _getc();	//允许类型名后面有空字符
    		if(c != ',') throw("CE: Unexpected character. ");	//不是","也不行
    		c = _getc();
    		while(c == ' ' || c == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') c = _getc(); //先把前面的空字符吞了
    	if(c == '-') {	//如果是"-",那就是负的
    		expr->symbol = -1; 
    		c = _getc(); while(c == ' ' || c == '
    ' || c == '	' || c == '
    ') c = _getc();	//因为读到了符号,所以主体还在后面
    	}
    	else if(c == '+') {	//如果是"+",那就是正的
    		expr->symbol = 1;
    		c = _getc(); while(c == ' ' || c == '
    ' || c == '	' || c == '
    ')	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 == '
    ' || c == '	' || c == '
    ')) 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 == '
    ' || c == '	' || c == '
    ') 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 == '
    ' || c == '	' || c == '
    ') c = _getc();
    	}
    	ins->type = 0;
    	c = _getc();
    }
    
    void readset(Instruction *ins)	//读取set
    {
    	ins->type = 1;
    	readexpression(',', ins->exp1);	//以逗号为结束标志读取exp1
    	readexpression('
    ', ins->exp3); //以换行为结束标志读取exp2
    }
    
    void readyosoro(Instruction *ins)	//读取yosoro
    {
    	ins->type = 2;
    	readexpression('
    ', ins->exp1);	//以换行为结束标志读取exp1
    }
    
    void readihu(Instruction *ins)	//读取ihu
    {
    	ins->type = 3;
    	ins->judgetype = readjudge();	//读取比较类型
    	c = _getc();
    	readexpression(',', ins->exp1);	//以逗号为结束标志读取exp1
    	readexpression('
    ', 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('
    ', 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 == '
    ' || c == '	' || c == '
    ') c = _getc(); //读到不是空字符为止
    		if(c == '{' || c == ':')	//指令
    		{
    			std::string opt;
    			c = _getc();
    			while(c == ' ' || c == '
    ' || c == '	' || c == '
    ') c = _getc(); //读到不是空字符为止
    			while(c >= 'a' && c <= 'z') { //获取指令名称
    				opt.push_back(c);
    				c = _getc();
    			}
    			if(c != ' ' && c != '	' && c != '
    ' && c != '
    ') 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 == '
    ' || c == '	' || c == '
    ') 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("
    ");
    	} catch (const char* e) {
    		cerr << e << endl;
    	}
    	return 0;
    }
    
  • 相关阅读:
    RabbitMQ消费端自定义监听器DefaultConsumer
    RabbitMQ exchange交换机类型
    RabbitMQ 快速入门
    chrome jsonView插件安装
    谈谈令人头大的prototype 和__proto__
    原生JS写一个淡入淡出轮播图
    模拟聊天对话框
    全选反选的小案例
    原生js做一个简单的进度条
    点击回到顶部的按钮
  • 原文地址:https://www.cnblogs.com/cjrsacred/p/9769969.html
Copyright © 2020-2023  润新知