NC编译器结题以及软件重用性的思索

经过3月29日到今天一共17天的紧张学习赶工,NC编译器的编写环节暂时告一段落。从开始的简单制导翻译到后来的词法语法分析,虽然其中还有很多细节没有学到位,但在与无数BUG的斗争中大体上已经领略到了一个编译前端的结构。这里做一下总结:
输入一段代码、一些规则,按规则输出代码中的信息,这就是一个编译器的工作。看似简单,但如果规则较复杂,不进行理论有序的描述就很难得到正确的结果。因此,把编译过程划分为几个部分,其中最前端的部分是词法分析,它读入源代码的字符串,根据语言的字符集提取其中的词法单元;其次是语法分析部分,语法分析读入词法分析返回的词法单元,根据语法规则对词法单元序列进行处理;语法分析之后是对语义的分析,语义即一段代码的含义,可以通过制导翻译的方法将代码或者属性附到语法分析产生的语法树的内部结点中,从而进行计算得到结果。

之前的代码主要写到了语法分析部分,语义的部分很难写出来,这是因为词法、语法部分我都想做成可重用的,即可以根据配置文件的词法、语法规则改写翻译不同的代码,但是语义部分却很难同样地实现。如果使用配置文件来描述语义,则相当于我还需要一个语言的编译器来编译语义的文件,因此我将词法、语法部分封起来,当做一个库使用,而语义部分则交给用户(使用库的人)来完成。
因此程序的最后部分是这样完成的:
语义动作以及语义单元:

//语义动作
struct smt_action{
    function<string(int,vector<string>&)> func;//第一个参数为选择哪个产生式 第二个是参数表
        //其他参数?
};
struct smt_unit{
public:
    static map<production*,smt_action*>table;

    static void init();
private:
    production*pd;//产生式指针
    int cho;//选择哪一个产生式
    int cur;//当前计算完成数
    string result;//计算结果
    bool set_func;
    function <string(int,vector<string>&)> func;//动作
public:
    vector<string>value;//完成的值
    void calculate(syn_unit*);//求值 传入语法单元
    void calculate(smt_unit*);//求值 传入其他语义单元的结果
    bool is_calcu_over;
    void output();//输出值
    smt_unit(production*,int);
};

编译器基类:

//分析器基类 需要添加函数到各语义动作中
class parser{
    lex_parser*lex;
    nc_parser*grammar;
    string codefile;
    smt_action basic_smt;

public:
    parser(string code, string gram = "NC\\grammar.txt", string tk = "NC\\token.txt")
        :codefile(code){
        lex = new lex_parser(tk);
        lex->init(code);
        grammar = new nc_parser(gram, lex);
        grammar->initial();

    }
    void run(){
        grammar->translate();
    }
    //向函数表中添加语义动作函数,name为产生式的头名
    void addfunc(string name, function<string(int, vector<string>)> func){
        auto p = grammar->get_production(name);
        if (p){
            basic_smt.func = func;
            smt_unit::table[p] = new smt_action(basic_smt);
        }
    }
};

这里实现重用的方法是,要求用户继承parser类,以添加自己的初始化部分,并且通过给定的宏来添加语义函数,对其产生式进行语义输出。语义动作函数有严格的格式(这也是这个程序的局限性之一),必须是指定的类型。
用户使用示例如下:

//使用者通过继承parser类实现语义定义
class Cusr_parser :public parser{

private:
    int addrs_num;//地址数
    stack<int> stmt_num_stk;//各条指令的地址数
    bool addrs_count;

    stack<addrs_word>stk_instruct;//地址字栈 存储每个当前语句下的指令
    stack<statement> stk_stmt;//语句栈

    vector<statement> table_stmt;
public:
    Cusr_parser(string s1,string s2,string s3):parser(s1,s2,s3){
        addrs_count = false;
        addrs_num = 0;
    }
    //地址的语义动作
    string addrs(int i, vector<string>&args){
        if (i > 7)return "error";
        if (addrs_count == false){
            addrs_num = 0;
            addrs_count = true;
        }
        string str = "匹配addrs" + args[0];
        output(str);
        addrs_num++;
        return args[0];
    }

    //instruct的语义动作
    string instruct(int i, vector<string>&args){
        if (i == 1)return "";
        if (addrs_count==true){
            stmt_num_stk.push(addrs_num);
            addrs_count = false;
        }
        stk_instruct.push(addrs_word(args[0], args[1]));
        string str = "匹配instruct" + args[0];
        output(str);

        return str;
    }
    //stmt的语义动作
    string stmt(int i, vector<string>&args){
        if (i == 1)return "";
        statement t;
        string str = "匹配statement" + args[0];
        output(str);
        int count = stmt_num_stk.top();
        stmt_num_stk.pop();
        while (count--){
            t.inst.push_back(stk_instruct.top());
            stk_instruct.pop();
        }
        stk_stmt.push(t);
        return str;
    }
    void init(){
        ADD_SEM_FUNC(Cusr_parser, addrs);
        ADD_SEM_FUNC(Cusr_parser, stmt);
        ADD_SEM_FUNC(Cusr_parser, instruct);
    }
    void show(){
        while(!stk_stmt.empty()){
            table_stmt.push_back(stk_stmt.top());
            stk_stmt.pop();
        }
        for (auto&t : table_stmt){
            t.show();
        }
    }
};

宏定义如下,用于实现对象与函数的绑定:

#define ADD_SEM_FUNC(clsname,name) addfunc(#name,bind(&##clsname##::##name,this,_1,_2))

在运行完之后,stk_stmt语句栈中将存储着指令的数据,用户可以根据自己的需求翻译为目标代码或者直接运行。运行结果如下:
这里写图片描述

我个人对于可重用的软件有一种谜之执着,不管写什么程序,都会考虑其重用性,写完只能跑一次的代码我一般是不会写的。但对可重用代码质量的考察也将是很严格甚至于苛刻的,毕竟要拿去无数次的用。反面教材比如我这次的程序,其效率完全不适合拿来通用(多半是要赶工毕设的锅),但其中所体现的重用性思想我觉得值得思考。比如配置文件的方法、利用继承和宏等等。而且,有通用也必然会带来一些约束,比如之前语义动作的格式,这不仅对效率有一定的影响,对使用者也带来一定的不便。要更优化地实现重用,还需一定的磨练。

在《形式语义学基础与形式说明》的前言中读到,如今的信息大草原已经不是当初的那片荒原,经过几十年的发展已经充满了各种软件资源,我们面临的是更加面向主体的程序设计和行为开发方法。因此,如何更准确地描述需求、行为将是一个重要的课题。

之前的文章链接:
制导翻译概述:http://blog.csdn.net/u011602557/article/details/68111472
简单翻译程序实现:http://blog.csdn.net/u011602557/article/details/68943237
词法分析:http://blog.csdn.net/u011602557/article/details/69763334
语法分析(预测分析法):http://blog.csdn.net/u011602557/article/details/69938255 ; http://blog.csdn.net/u011602557/article/details/70064284
语法制导定义:http://blog.csdn.net/u011602557/article/details/70147540

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章