去年这个时候,我还在瞻仰这道题而无从下手,转眼一年又过去了啊,时间过得好快
写解释器大概有两种动手方法:一种是程序员的思路,每次实现一点功能并调试,逐渐实现全部功能;另一种是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;
}