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

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