編譯原理學習筆記(三)詞法分析

在上一次對第二章的學習中(http://blog.csdn.net/u011602557/article/details/68943237),利用制導翻譯的內容做了一個簡單的翻譯程序。其結構比較混亂,語法分析和詞法分析基本混在了一起。在第三章中着重講了詞法分析的內容,其中說到了將分析部分分爲語法分析和詞法分析的技術原因(龍書第二版69頁3.1.1),同時也有利於理解編譯過程。因此這一次的內容主要圍繞其一的詞法分析展開。

詞法分析是編譯第一階段,詞法分析器的主要任務是讀入源程序的字符,將其組成詞素,生成一個詞法單元序列。
詞法單元表示一類具有相同屬性的單詞,由詞法單元名和可選的一個屬性值組成。詞素是源程序中的一個字符序列,它符合某個詞法單元的模式,成爲詞法單元的一個實例。比如1.2 23 都是數(number),1.2和23是詞素,number是詞法單元名,其模式以正則表達式書寫就是:[0-9]+.?[0-9]+。
詞法分析器還需要和符號表交互,在識別到一個標識符的詞素時,需要將其添加到符號表中,語法分析器需要從符號表中得到標識符的信息。

這裏依然以NC程序的翻譯作爲例子,其結構簡單,沒有變量標識符,所以不需要符號表。但爲了提高NC代碼的靈活性和通用性,這個詞法分析器可以通過配置文件來生成一個詞法單元表,存儲詞法單元。
token.txt文件內容如下:
O O
G G
N N
M M
T T
F F
S S
X X
Y Y
Z Z
NUM [+-]?[0-9]+[.]?[0-9]+
END ;
這個表和上次的模式表有一定相似性,但不同的是這是用來進行詞法分析的而非語句分析。前者表示詞法單元名,後者表示模式,其屬性值將由其散列值決定(散列方法爲其名字第一個非重複的字符),表的大小固定256,詞法單元數不能超過這個數。

詞法單元結構如下:

struct lex_token{//詞法單元
    string token;
    int id;
    regex reg;//模式
    smatch result;
    lex_token(string tk, string r, int i) :token(tk), reg(r), id(i){
        ;
    }
    inline bool pattern(string&str){
        return regex_search(str, result, reg) && result.position()==0;//必須是緊接着的匹配
    }
    bool compare(string&str){
        for (int i = 0; i < token.length(); i++){
            if (str[i] != token[i])return false;
        }
        return true;
    }
};

生成詞法單元表後,要使用它有些麻煩。因爲讀入一串字符,要得到其token的話,需要在這張表中找一個與其匹配的詞法單元,每次都要掃一遍表,很麻煩。在通常的詞法分析程序設計中,這裏會被設計爲跳轉表的形式,根據讀入的字符跳轉到對應的程序進行處理,而由於這個詞法單元表是讀入配置文件生成的,不能以通常的方法來做。
這裏使用了一種“記憶表”的方法,在每次讀入token後,首先根據其第一個字符查詢記憶表,如果有且符合模式,則直接返回指針,如果沒有,則遍歷表來獲得指針,同時存入記憶表。表結構如下:

struct mem_table{//記憶鏈表
    lex_token*p;
    mem_table*next;
    mem_table(lex_token*pc) :p(pc), next(NULL){ ; }
    ~mem_table(){
        if (next){
            next->~mem_table();
            free(next);
        }
    }
    lex_token*find(string&str){
        if (p->pattern(str))return p;
        else if (next)return next->find(str);
        else return nullptr;
    }
    void add(lex_token*pc){
        if (next)next->add(pc);
        else next = new mem_table(pc);
    }
};

詞法分析模塊最終結構如下:


//詞法分析
class lex_parser{//詞法分析模塊:生成用於語法分析的token鏈 使用散列法存儲 最多256種詞法單元

private:
    vector<lex_token*> tokens = vector<lex_token*>(256, NULL);//詞法單元表
    vector<mem_table*> mtable = vector<mem_table*>(256, NULL);//記憶表
    int line = 0;
public:
    lex_parser(string filename)
    {
        ifstream fin(filename);
        string buf1, buf2;
        while (fin >> buf1){
            //設計詞素時要盡力避免二次探查 如有二次 則詞素長度要大於1 否則映射失敗
            fin >> buf2;
            int cur = 0;
            while (buf1.length() > cur && tokens[buf1[cur]])cur++;
            if (buf1.length() <= cur)report("詞素token設計有誤!");
            else{
                tokens[buf1[cur]] = new lex_token(buf1, buf2, buf1[cur]);
            }
        }
    }
    void init(){
    }
    ~lex_parser(){
        for (auto&p : tokens){
            if (p) delete p;
        }
        for (auto&p : mtable){
            if (p) delete p;
        }
    }
    lex_token*find_token(string&str){//尋找詞法單元
        int cur = 0;
        while (cur<str.length() && !tokens[str[cur]]->compare(str))cur++;
        if (cur >= str.length())report("詞法錯誤,行數:" + line);
        else{
            return tokens[str[cur]];
        }
        return NULL;
    }
    lex_token*find_pattern(string&str){//尋找符合的模式
        lex_token*ptr = nullptr;
        if (mtable[str[0]])
            ptr = mtable[str[0]]->find(str);
        if (ptr)
            return ptr;
        else{
            for (auto&p : tokens){
                if (p && p->pattern(str)){
                    if (mtable[str[0]])mtable[str[0]]->add(p);//記憶查詢結果
                    else mtable[str[0]] = new mem_table(p);
                    return p;
                }
            }
        }
        return nullptr;
    }
    lex_token* getNextToken(string&str, int&pos){
        while (str[pos] == ' ' || str[pos] == '\n' || str[pos] == '\t')pos++;//跳過空白字符
        if (pos >= str.length()){
            line++;
            return nullptr;
        }
        string temp(str.begin()+pos, str.end());
        auto ptk = find_pattern(temp);//尋找符合匹配的詞法單元
        if (!ptk)report("詞法錯誤,行數:" + line);
        else{
            regex_search(temp, ptk->result, ptk->reg);
            pos += ptk->result.length();//光標前移
            return ptk;
        }
        return nullptr;
    }

};

在main中添加測試:

int main(){
    lex_parser*LX = new lex_parser("NC\\token.txt");
    string code = "N001 G01 X-100 Y20 Z+35 M03 S40 T50 F40;";
    int pos = 0;
    while (pos < code.length()){
        auto ptr = LX->getNextToken(code,pos);
        if (ptr)
            cout << ptr->token << endl;
    }
    system("pause");
    return 0;
}

運行結果如下:
這裏寫圖片描述

總結:
詞法分析是編譯第一步,是後續分析的基礎。本文在詞法分析基礎上,增加了配置文件設置詞法單元表的方式,可以靈活地建立詞法規則,增強了其通用性,但沒有考慮符號表的設置,因此目前只適合NC類的程序。

遇到的問題:
1. 調試過程中,構造函數中this指針變成NULL,構造函數後退出後指向一個未知領域,其中的變量無法使用。
猜想可能的原因:內存越界覆寫了this指針
最終解決方法:將編譯器模式從Realese改爲Debug模式再調試
參考:http://bbs.csdn.net/topics/390624297
http://jingyan.baidu.com/article/8ebacdf026993249f65cd58d.html?st=2&os=0&bd_page_type=1&net_type=2

2 .regex_match()無法匹配字符串中的一部分
原因:regex_match()匹配全字符串,含有其他部分則不會匹配。
解決方法:使用regex_search()

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