1 条题解
-
0
自动搬运
来自洛谷,原作者为

星野梦美
**搬运于
2025-08-24 21:51:41,当前版本为作者最后更新于2018-10-12 00:42:00,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
去年这个时候,我还在瞻仰这道题而无从下手,转眼一年又过去了啊,时间过得好快
写解释器大概有两种动手方法:一种是程序员的思路,每次实现一点功能并调试,逐渐实现全部功能;另一种是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; }
- 1
信息
- ID
- 2687
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者