洛谷 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;
}

转载于:https://www.cnblogs.com/cjrsacred/p/9769969.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值