C編譯器剖析 2 2 詞法分析

2021-06-28 09:38:58 字數 2773 閱讀 3016

2.2  詞法分析

目錄ucc\ucl下,與詞法分析相關的c檔案主要有input.c和lex.c,input.c用於從外存讀入預處理後的檔案,其主要的函式如圖2.2.1所示。在ucc驅動的**中,已經預定義了巨集_ucc,所以第39行的條件成立,函式readsourcefile()會使用c標準庫的io函式來讀取檔案。因為.c檔案只是普通的文字檔案,其檔案大小很少超過1m位元組的,所以ucc並沒有採取逐行讀的行快取策略,而是一下把整個檔案讀入記憶體中,第45行的fopen()用於開啟檔案,通過第56行的fseek()函式和第57行的ftell()函式,就可以獲得檔案的大小,然後通過第59行的malloc()開闢了足夠大的記憶體堆空間,第67行通過fread()把整個檔案讀入記憶體中。

圖2.2.1 readsourcefile()

此後,檔案lex.c的詞法分析器就不再需要去讀寫檔案了,只需要從記憶體中讀取字元即可。標頭檔案ucc\ucl\input.h中的結構體structinput記錄了關於輸入檔案的資訊,如圖2.2.2所示,其中第15行的base域記錄了讀入的輸入檔案在記憶體中的起始位置,而cursor則反映了當前的讀取位置。對乙個名為hello.c的c檔案而言,ucc編譯器見到的實際上經預處理後的檔案hello.i。檔案hello.i包含了各個標頭檔案及c檔案hello.c,為了在錯誤處理時能準確報告出錯位置,引入了圖2.2.2第4行的結構體struct coord。其中,coord是coordinate的縮寫,代表「座標」之意,其中包含了出錯的源檔名filename,比如hello.c;出錯的行號ppline,這個行號指的是在hello.c中的行號;而line代表的在預處理後的檔案hello.i中的行號;出錯的列號col。

圖2.2.2 struct input

在ucc\ucl\error.c的do_error()函式中,我們會看到如下用於報告出錯位置的**。

void do_error(coord coord, const char *format, ...)

在ucc源**中,我們使用error()來報錯,而不是直接使用do_error(),error的巨集定義如下所示。這麼做的主要目的是為了方便ucc編譯器的除錯。

#define     error         fprintf(stderr, "(%s,%d):",__file__, __line__) , do_error

通過error(),我們可以得到如下所示的出錯提示,(hello.c,3)告訴我們,程式設計師編寫的hello.c的第3行缺少了乙個分號,而我們是在ucc編譯器源**的stmt.c的第24行檢測到這個錯誤的。當然,真正把ucc編譯器作為乙個產品發布時,」 (stmt.c24):」這樣的資訊是沒有必要列印出來的。此時,只要把error的巨集定義改為」 #define         error  do_error 」即可。

(stmt.c 24):(hello.c,3):error:expect ;

接下來,我們來分析一下詞法分析器lex.c中的**。因為乙個字元對應乙個位元組,乙個位元組包含了0至255種不同的數值,所以我們可以寫乙個大大的switch語句,根據當前字元的值來分門別類地進行處理,如下所示。

switch(*cursor)

不過,一般情況下,當遇到乙個switch語句中有這麼多的case時,往往會採取「打表」的方法來簡化。圖2.2.3是ucc的詞法分析器getnexttoken的主要**,每呼叫一次getnexttoken()我們就會獲得乙個由若干個字元構成的單詞,第1165行的skipwhitespace()跳過了注釋、空格、製表符、回車和換行等空白符,真正進行詞法分析的是第1170行的**。檔案ucc\ucl\token.h中包含了ucc編譯器用到的各個token名稱。

圖2.2.3   getnexttoken()

我們用cursor所指向的當前字元值來作為陣列scanners的下標,字元(*cursor)的值正好在0至255之間,而陣列scanners即為乙個由函式指標構成的**。

typedef int (*scanner)(void);

static scanner       scanners[256];

這個函式指標構成的**scanners當然需要經過初始化,在main()函式中,我們呼叫了setuplexer()來初始化詞法分析器,其主要的工作就是初始化這個**,如圖2.2.4所示。

圖2.2.4 setuplexer()

該**共有256項,各個以」scan」為字首命名的掃瞄函式的**不會太複雜,我們就不一一進行分析,僅以掃瞄標誌符為例,如圖2.2.5所示,第778至788行處理以字母l開始的形如l』a』的寬字元和形如l」abc」的寬字串。第796行需要再判斷一下識別出來的標誌符會不會是c語言的關鍵字,如果確實是c程式設計師宣告的標誌符(變數名或者函式名),我們就在tokenvalue中記錄其名稱。

圖2.2.5 scanidentifier()

編譯器 詞法分析

總結 詞法分析 字串流 mov sum,x 執行加法運算 單詞流 mov sum,x 屬性字流 token type instr token type ident token type comma token type ident語法分析token currtoken getnexttoken 從屬...

編譯器設計 詞法分析

通過python實現了乙個能夠識別單詞的程式,單詞定義為 以字母開頭的任意數字和字母的組合 1.re模組 定義字母和數字pattern,通過match對字元進行匹配 2.enum模組 用來定義識別單詞過程中的狀態,這裡定義了 初始態過程態 完成態錯誤態 開始識別單詞 已經識別單詞的一部分 識別到乙個...

編譯器之詞法分析

最近我們在做乙個有關snl語言的編譯器,下面寫了一下大概流程 詞法分析器是編譯過程的第一階段,功能是 1.對以字串形式輸入的源程式 這裡是把源程式從檔案讀出,也可以在控制台輸入 按順序進行掃瞄,根據snl語言的詞法規則識別具有獨立意義的單詞 符號 序列,如保留字 由語言系統自身定義的,通常是由字母組...