程序語言的詞法分析與語法分析

計算機是無法對程序語言的產生人一樣的“理解”的,對於計算機一個程序只是一個字符串。因此要在計算機上運行一段程序就需要把程序語言轉化爲機器語言,這個過程就是“編譯”。編譯的第一步(通常稱爲前端)就是對程序語言做詞法分析和語法分析 。

詞法分析

詞法分析的任務是把一整串程序代碼切分成一個一個的token,token就是數字、變量名、關鍵字、函數名這些程序語言中的基本單位。我們在這裏考慮最簡單的While語言,它包括以下token:運算符(+,-,*,/,...)、賦值符號(=)、間隔符((,),{,})、自然數(0,23,...)、變量名(x1,yyy,__x,...)、關鍵字(if,while,var,...)、函數名(read,write)。例如,對於表達式(1+x)*y ,我們期待詞法分析結果爲TOK_LEFT_PAREN TOK_NAT(1) TOK_PLUS TOK_IDENT(x) TOK_RIGHT_PAREN TOK_MUL TOK_IDENT(y)

因此,詞法分析本質上是做字符串匹配——用token匹配程序代碼。我們可以用“正則表達式”這一語言來描述字符串(token),正則表達式\(r\)遞歸定義如下:\(r=\epsilon\)表示空串;\(r=c\)表示單個字符\(c\)\(r=r_1|r_2\)表示\(r_1\)\(r_2\)\(r=r_1r_2\)表示\(r_1\)\(r_2\)首位連接;\(r^*\)表示空或\(r\)\(rr\)\(rrr\cdots\)。通常我們有一些慣用的簡寫,用[a-cA-C]表示a|b|c|A|B|C;用r?表示r|ε。於是變量名的正則表達式寫作[_a-zA-Z][_a-zA-Z0-9]*,自然數的正則表達式寫作0|[1-9][0-9]*

匹配字符串的最常用辦法就是轉化爲在自動機上做狀態轉移(走路)。我們很容易把正則表達式轉化爲一個非確定性有限狀態自動機(NFA,Nondeterministic Finite Automata),在這種自動機上狀態轉移的字符可以是ASCII碼或\(\epsilon\)(空),同時從一個狀態出發一個字符有多種轉移方式。我們在自動機上選出起點狀態和若干終點狀態,期待從起點出發能到達終點當且僅當走過的所有狀態轉移字符一起串成的字符串落在正則表達式內。爲此,對於單個字符\(c\)只需要一個起點和終點中間連接\(c\)這一條狀態轉移邊;空字符\(\epsilon\)只需要一個起點和終點以及一條\(\epsilon\)邊;\(r_1|r_2\)我們從一個起點出發連兩條\(\epsilon\)邊連向\(r_1,r_2\)的自動機的起點,再讓它們的終點以\(\epsilon\)邊連向一個終點;\(r_1r_2\)只需把\(r_1\)的終點用\(\epsilon\)邊連向\(r_2\)的起點;對於\(r^*\),我們從\(r\)的終點連一條\(\epsilon\)邊向\(r\)的起點,再從起點用一條\(\epsilon\)邊連向\(r\)的起點。至此,我們已經能把一切正則表達式轉化爲NFA了。

然而在實踐中我們是不可能用NFA來匹配字符串的,因爲從一個狀態出發我們並不知道應當選擇哪條路徑。而接下來我們說明,我們可以高效地把NFA轉化爲DFA(有限狀態自動機,Deterministic Finite Automata),這種自動機的轉移邊只有ASCII碼而沒有\(\epsilon\)邊,從一個狀態出發一個字符只有一種轉移方式。方法是:DFA的每個狀態是NFA狀態的集合。從起點出發,把所有隻經過\(\epsilon\)邊能到達的狀態集合作爲DFA的起始狀態。對於每個DFA的狀態,它經過字符\(c\)能到達的狀態是,把NFA中這些狀態對應的狀態只經過\(\epsilon\)邊或\(c\)邊能到達的新狀態收集起來作爲一個新的狀態集合。可以證明這樣構建得到的一定是正確的DFA。

語法分析

在詞法分析以後,我們希望能夠得到表達式和程序語句的抽象語法樹。例如對於(1+x)*y,我們希望根節點爲*,左兒子爲+,右兒子爲y+的兒子分別是1x

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