1. 程式人生 > >編譯原理——詞法分析程式

編譯原理——詞法分析程式

前言:這是我學習編譯原理,課程實驗的內容,課程早已結束,現整理髮表。

一、實驗任務

  1. 閱讀已有編譯器的經典詞法分析源程式;
  2. 用C或JAVA語言編寫一門語言的詞法分析器。

二、實驗內容

  1. 閱讀已有編譯器的經典詞法分析源程式。
    選擇一個編譯器,如:TINY或PL/0,其它編譯器也可(需自備原始碼)。閱讀詞法分析源程式,理解詞法分析程式的構造方法——狀態圖程式碼化。尤其要求對相關函式與重要變數的作用與功能進行稍微詳細的描述。若能加上學習心得則更好。

  2. 根據該語言的關鍵詞和識別的詞法單元以及註釋等,確定關鍵字表,畫出所有詞法單元和註釋對應的DFA圖。

  3. 仿照前面學習的詞法分析器,編寫選定語言的詞法分析器。

  4. 準備2~3個測試用例,要求包含正例和反例,測試編譯結果。

三、詞法分析

tiny詞法解析

  • tiny的記號
    tiny語言的記號

可以看出,tiny語言的記號很少,畢竟只是簡單的語言。但一開始時想——這些怎麼存的,又是怎麼判斷的,我一開始也是很懵逼的,記得我當時也是花了一下午看了tiny的詞法分析原始碼和實驗給的文件,才弄懂該怎麼寫,然後回到宿舍一晚上就在tiny的基礎上改寫完了。<( ̄︶ ̄)>

簡單的說,記號就是一個語言的分類,當你讀取字串的時候,你總得識別出這一段字串是什麼,是什麼型別的,所以你按某個規則讀完玩一段字元,你就該判斷這段字元是什麼,是否有錯,怎麼歸類,而記號就是你用來歸類的標準。

  • 狀態轉換

然後既然有了型別,那怎麼判斷型別呢?用DFA轉換圖。

字元數字識別符號DFA

這個轉換如很簡單,START 開始狀態,INNUM 數字,INID 字串,+-*/=<(); 特殊符號,DONE 狀態,從開始狀態根據又一次 讀取第一個字元,根據讀取的字元轉換狀態,進入某個狀態後,當讀取的下一個字元不符合當前狀態的型別,就不讀取字元,而讀取完的這段字串,當是某個型別。判斷完這段字串,又一次 重複操作,直到讀取所有字元。

把需要判斷的狀態都加上時,就成了tiny的詞法分析轉換圖。

這裡寫圖片描述

四、程式設計

我寫的詞法分析其實就是在tiny基礎上稍加修改而已,詞法設計這部分大致相同。

記號

保留字:
cin while then cout end
特殊字元:
= + - * / ( ) ; >> <<
註釋:
{這是註釋}

轉換表
參照上面

程式碼解析

  • 巨集定義最大匹配字元變數的長度為40
    保留字為5個
#define MAXTOKENLEN 40
#define MAXRESERVED 5
  • 定義一個列舉型別,用於表示dfa的狀態集
typedef enum { //DFA狀態集
    START,
    INCOMMENT,
    INNUM,
    INID,
    ININ,
    INOUT,
    DONE
} stateType;
  • 定義個列舉型別,表示根據輸入的字串匹配到的字串型別。
typedef enum { //用於匹配的型別,判斷輸入
    /* 異常狀態 */
    ENDFILE,
    ERROR,
    /* 保留字 */
    CIN,
    COUT,
    WHILE,
    THEN,
    END,
    /* DFA狀態 */
    ID,
    NUM,
    /* 特殊符號 */
    IN,
    OUT,
    EQ,
    PLUS,
    MINUS,
    TIMES,
    OVER,
    LPAREN,
    RPAREN,
    SEMI
} tokenType;
  • 定義一個結構體,用於根據匹配到的保留字輸出保留字。
static struct //保留字結構體,用於輸出
{
    const char *str;
    tokenType tok;
} reservedWords[MAXRESERVED] = {
    {"cin", CIN},
    {"while", WHILE},
    {"then", THEN},
    {"cout", COUT},
    {"end", END}};
  • 定義一個返回值為bool型別的函式,判斷輸入字元是否為字母。
bool isLetter(char c) //是否為字元
{
    if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
    {
        return true;
    }
    else
    {
        return false;
    }
}
  • 判斷匹配的字串是否為保留字。
static tokenType reservedLookup(char *s)
{
    for (int i = 0; i < MAXRESERVED; i++)
        if (!strcmp(s, reservedWords[i].str))
            return reservedWords[i].tok;
    return ID;
}
  • 根據匹配到的字元型別輸出字串。
void printToken(tokenType token, const char tokenString[]); //輸出函式
  • 匹配字串的函式。
void getToken(string ss); // 詞法分析

五、實驗測試

  • 輸入
{sdfs
adf}
cin  >>{sdfsadf} x;
{sdfsadf}
cin>>y;
while (cin>>z) then{sdfsadf}
x=x z y;
cout << x;
end;
  • 輸出
1:{sdfs
2:adf}
3:cin  >>{sdfsadf} x;
        3: reserved word:cin
        3: >>
        3: ID, name= x
        3: ;
4:{sdfsadf}
5:cin>>y;
        5: reserved word:cin
        5: >>
        5: ID, name= y
        5: ;
6:while (cin>>z) then{sdfsadf}
        6: reserved word:while
        6: (
        6: reserved word:cin
        6: >>
        6: ID, name= z
        6: )
        6: reserved word:then
7:x=x z y;
        7: ID, name= x
        7: =
        7: ID, name= x
        7: ID, name= z
        7: ID, name= y
        7: ;
8:cout << x;
        8: reserved word:cout
        8: <<
        8: ID, name= x
        8: ;
9:end;
        9: reserved word:end
        9: ;

六、實驗總結

實際上剛寫這篇部落格的時候我有點虛,因為不怎麼記得tiny的語法分析怎麼回事了,只能硬著頭皮有看了下實驗文件,然後才慢慢回想起來。然後就是你們看到的部落格了。

當時我確實是花了下午2,3個小時看這實驗的文件的,看實驗文件,看tiny原始碼,慢慢理解究竟是怎樣的,然後規劃自己要改成什麼樣的,實驗內容那部分我刪改了些,有部分內容要求是我們確定了我們的語言後,就鼓勵我們自己定義一門語言,我當時就是想寫們專門計算數學的語言,也就才有了這樣的輸入。

然後事實上,並不好寫,尤其是要寫整個前端的情況下,越發的力不從心,我後面的實驗深刻理解到了這一點,然後沒有堅持寫出自己的語言,事實上也沒有一個同學寫出自己的語言的整個前端,不是tiny就是pl改的。

還有,我也是在這裡入了列舉和結構體的坑,然後有次實驗程式碼瘋狂用了結構體,搞得結構很複雜,總之,如果你們還看我後面實驗的的程式碼,你就知道什麼叫做喪心病狂了。。。

七、資料下載