(轉載請表明出處 http://www.cnblogs.com/BlackWalnut/p/4467749.html )
當我們寫好一份源代碼,提交給編譯器的時候,這是編譯器對我們提交代碼進行詞法分析。這個整個編譯過程的第一步。詞法分析器將我們的提交的代碼看作是一個文本,它工作的目的就是將這個文本中不符合我們所使用的語言(c++或者java)的單詞(字符串)挑選出來,以及將符合語言的單詞(字符串)進行分類。
對於第一個功能,挑選不合格字符串,我們可以這樣理解。例如,對於c++而言,定義變量名不能使用數字開頭,那么如果出現 int 1tmp;語句就是非法的。
對於第二個功能,我們一方面要確定怎么解讀一個字符串。例如,if32,是將其看成為if 和 32 ?還是將其看成if32?另一方面,我們要將其讀到的字符串進行分類,例如,if 是內部關鍵字,它是屬於IF類.但是對於int tmp; 中的tmp,它對於語言(c++或者其他語言)而言,就是一個id,因此它屬於ID,而前面的int,則是INT.再比如對於43,它是一個常量,也是一個NUM,3.14則是一個REAL。
那么對於一段代碼 int a = 10 , b = 20; int c ; c = a + b ; 經過詞法處理后得到的輸出為
INT ID ASSIGN NUM COMMA INT ID ASSIGN NUM SEMI(;) INT ID SEMI ID ASSIGN ID PLUS ID SEMI
上面的輸出的符號,將用於語法分析時使用。很顯然,語法分析只專注於分析一個單詞是否合法,以及分類,並不處理單詞與單詞之間的關系是否合法。例如,
void func() { // action} ; int a = func ;
對於第二個語句,詞法分析並不關心其是否正確,照樣輸出 INT ID ASSIGN ID SEMI.
了解了詞法分析的作用后,乍一看,詞法分析面對的處理對象似乎是一個單詞,例如,先讀入一個單詞,判斷這個單詞的類型,再讀入一個,判斷類型,and so on。但是,如何判斷是否讀入了一個“正確”的單詞呢?例如 , c=a+b 和 c = a + b 兩個都是合法的c++語句。對於后者而言,可以用空格來表示讀完一個單詞,但是對於前者,詞法分析器是將c=a+b看成一個單詞呢?還是將c看成一個單詞 ?
所以,詞法分析器是將一個字母作為分析目標,根據該字母的下一個字母來判斷是否應該判定該字母所屬類型。對於c=a+b而言,當讀入a時,詞法分析其將其標記為ID狀態,當讀入=號時,則表示a的狀態已經可以確定,則將a的狀態(ID)輸出。再舉個例子,tmp=c-d,當詞法分析器讀入t時,將其標記為ID狀態,讀入m和p時,狀態不變,當讀入=時,輸出當前狀態,並退回到其實狀態,然后對讀入的=進行分析。用畫圖來表示就是:
(我保證這是我所有博客中唯一一張手繪圖,務必珍惜。。。。。沒見過健身完手抖的么)
方框2就是狀態ID,狀態1時開始狀態,當讀入t時,到達狀態2.當讀入m時轉一圈回到2(上面標有m的帶箭頭的曲線,對的,曲線,不是頭上的犄角),讀入p時,也還回到狀態2。當讀入p時,發現沒有地方可以走了,就輸出狀態2,也就是ID,然后回到狀態1,如果狀態1有一條標有=的曲線指向另一個狀態,那么就沿着那條曲線(我們成為邊)到另一個狀態。
我們把上圖叫做一個狀態機,因為它的狀態時有限的,且只要讀進一個字符我們就能確定找到唯一一條邊,或者時找到一個狀態,所以我們成為確定有限自動狀態機(DFA)。
這樣我們就知道每一個或者多個狀態確定一個語言中的一個分類(初始狀態除外),那么對於ID狀態,c++中要求ID的不能用數字開頭,那么就可以得到一下一個狀態機,
(咳咳。。。。額,大家時來學知識的,不要在意保證不保證的這些細節,這不重要)
上圖就描述了一個確定有限自動狀態機,當在狀態1的時候,讀入任何以a-z和A-Z的字符都將進入狀態2,在狀態2中讀入任何0-9,a-z,A-Z,都將回到狀態2.如果讀入一個其他的字符,狀態機輸出狀態2(ID),然后回到狀態1.(趕緊隨便定義一個變量名試試吧)。
這樣,只要將一個語言中的所有分類分別用一個DFA表示出來,再將各個DFA連接到一起,就可以對一個語言的源代碼進行分析了。例如以下:

可以看出,這個大的狀態機並不是簡單的將幾個小的狀態機連接起來,還涉及到一些合並操作。
有以上可以看出來,確定一個語言的狀態機,要解決兩個問題,第一,如何描述語言中的一個類別,例如 REAL,IF,ID。第二,如果將各個小的狀態機連接起來,組成一個大的語言確定有限狀態機。
對於第一個問題,我們引入正則表達式(也稱作正規式)。正則表達式的規定如下:

那么,如果要描述ID ,則其正則表達式為[a-zA-Z]+[a-zA-Z0-9]* .正則表達式主要作用是使用flex詞法分析器生成器。
對於第二問題,我們引入非確定有限狀態機(NFA)。相比於確定有限自動狀態機,非確定有限自動狀態機中有一條或者多條邊可以為空,或者有標有同一個符號的兩條指向不同狀態的邊。例如下面兩幅圖:


因為,當讀入一個數據的時候,我們無法確定到底選擇哪一條邊,所以,是非確定的。
那么,將一個正則表達式轉化成為一個確定有限狀態機的過程為:正則表達式轉化為非確定有限狀態機,非確定有限狀態機轉化為確定有限狀態機。
正則表達式轉化為非確定有限狀態機,可以使用下圖:

這樣將語言中的所有類分別使用正則表達式表示出來,然后將每個正則表達式表示成一個小的非確定有限制狀態機,然后使用一個新的狀態作為起始狀態,用空邊將該起始狀態和其他小的非確定有限狀態機的起始狀態連接起來,就得到該語言的非確定有限狀態機。
為了將非確定有限狀態機轉化為確定有限狀態機,我們引入ε閉包的概念:將狀態集合A(包含一個或者多個狀態)和使用空邊和A連接的狀態組成的新的狀態集合,得到的新的狀態集合成為closure(A),那么就有如下等式:

等式左邊表示為:狀態集合d吃掉一個標示符c后能夠到達的狀態的新狀態集合(假設為)h。注意到因為有closure,所以h還包含使用空邊和h中所有狀態連接的狀態。edge(s,c)標示從s出發,吃掉c后能到達的所有狀態的集合。
我們根據以上等式,將下面的左圖(NFA)轉化為右圖(DFA):


上圖轉化描述為:對於左圖狀態1,其ε閉包為A(1,4,9,14),就是右圖中的開始狀態集合。可以看出,這個集合A可以吃掉的字符包括i,a-z,0-9,以及任何字符(就是A中所有狀態能夠吃掉的字符的集合),如果A吃掉i(就是對A中的各個狀態s求edge(s,i)),那么可以到達的集合為B`(2,5,15),再求closure(B `),則得到新的集合B(2,5,6,8,15)。然后考慮A吃掉(a-h,j-z),然后求閉包,那么得到集合C(5,6,8,15),等等。直到將A可以吃掉的字符都計算過后,我們就得到了上右圖中,和起始狀態集合連接的各個狀態集合。然后依次再對其他狀態集合進行相同操作。那么最終得到的式右圖。這樣就完成了NFA到DFA的轉化。
如果將右圖中的每個狀態集合看成一個狀態,那么就得到了一個確定有限自動狀態機。那么,對於右圖每個狀態(或者狀態集合),我們如何確定這個狀態(狀態集合)代表的是語言中的哪個類呢?也就是如何確定右圖中的每個狀態的上角標,如ID,NUM,IF等。這里有三個原則需要遵守:
第一:最長字符串原則。當我們遇到例如 if32i 這種字符串時,我們將對整個字符串進行匹配,而不是匹配到if就返回類型IF。
第二:終止狀態優先原則。在我們從NFA轉化到DFA過程中,如果一個狀態集合包含終止狀態,則在轉化后得到的DFA中,該狀態集合為終止狀態集合。
第三:規則優先原則。如果轉化后的狀態集合包含多個終止狀態,例如狀態集合B中包含8和15,那么指定一個具有更高的規則優先級,在上面的例子中,我們指定8的規則優先級高,那么最終B代表的是ID類。那么在程序中如何指定呢?就是使用flex過程中,定義越靠前的正則表達式規則優先級越高。至於flex怎么使用,這不是我們的重點,可以參考其他資源。
好了,以上就是所有的詞法分析過程,其實,最終我們是將一個DFA轉化為一個二維數組,如下:

這是一個DFA數組的一部分。這個轉化過程我們通常使用flex來完成。我們只要定義號一個語言的正則表達式,其他的NFA,DFA就直接交給flex來解決就可以了。但是,知道原理不是更讓人自由么?
以下是生成tiger語言的flex正則表達式(因為編碼(lan)的原因,所以,在輸出常量字符串的時候是一個字母一個字母的輸出的,很好改,可以自己動手改一下):
%{
#include <string.h>
#include "util.h"
#include "tokens.h"
#include "errormsg.h"
int charPos=1;
int count = 0 ;
#ifdef __cplusplus
extern "C" int yywrap (void )
#else
extern int yywrap (void )
#endif
{
charPos = 1 ;
return 1 ;
}
void adjust(void)
{
EM_tokPos=charPos;
charPos+=yyleng;
}
%}
%state COMMENT
%state CONST_STRING
%%
<INITIAL>[\"] {adjust(); yylval.sval = string(yytext) ; BEGIN CONST_STRING; return CONSTSTR;}
<INITIAL>" " {adjust(); continue;}
<INITIAL>\n {adjust(); EM_newline(); continue;}
<INITIAL>\t {adjust(); continue;}
<INITIAL>"," {adjust(); return COMMA;}
<INITIAL>: {adjust(); return COLON;}
<INITIAL>; {adjust(); return SEMICOLON;}
<INITIAL>"(" {adjust(); return LPAREN;}
<INITIAL>")" {adjust(); return RPAREN;}
<INITIAL>"[" {adjust(); return LBRACK;}
<INITIAL>"]" {adjust(); return RBRACK;}
<INITIAL>"{" {adjust(); return LBRACE;}
<INITIAL>"}" {adjust(); return RBRACE;}
<INITIAL>"." {adjust(); return DOT;}
<INITIAL>"+" {adjust(); return PLUS;}
<INITIAL>"-" {adjust(); return MINUS;}
<INITIAL>"*" {adjust(); return TIMES;}
<INITIAL>"/" {adjust(); return DIVIDE;}
<INITIAL>"=" {adjust(); return EQ;}
<INITIAL>"<>" {adjust(); return NEQ;}
<INITIAL>"<" {adjust(); return LT;}
<INITIAL>"<=" {adjust(); return LE;}
<INITIAL>">" {adjust(); return GT;}
<INITIAL>">=" {adjust(); return GE;}
<INITIAL>"&" {adjust(); return AND;}
<INITIAL>"|" {adjust(); return OR;}
<INITIAL>:= {adjust(); return ASSIGN;}
<INITIAL>for {adjust(); return FOR;}
<INITIAL>array {adjust(); return ARRAY;}
<INITIAL>string {adjust(); return STRING;}
<INITIAL>if {adjust(); return IF;}
<INITIAL>then {adjust(); return THEN;}
<INITIAL>else {adjust(); return ELSE;}
<INITIAL>while {adjust(); return WHILE;}
<INITIAL>to {adjust(); return TO;}
<INITIAL>do {adjust(); return DO;}
<INITIAL>let {adjust(); return LET;}
<INITIAL>in {adjust(); return IN;}
<INITIAL>end {adjust(); return END;}
<INITIAL>of {adjust(); return OF;}
<INITIAL>break {adjust(); return BREAK;}
<INITIAL>nil {adjust(); return NIL;}
<INITIAL>function {adjust(); return FUNCTION;}
<INITIAL>var {adjust(); return VAR;}
<INITIAL>type {adjust(); return TYPE;}
<INITIAL>int {adjust(); return INT ;}
<INITIAL>[0-9]+ {adjust(); yylval.ival=atoi(yytext); return NUM;}
<INITIAL>([0-9]+"."[0-9]*)|([0-9]*"."[0-9]+) {adjust(); yylval.fval=atoi(yytext); return REAL;}
<INITIAL>[a-zA-Z][a-zA-Z0-9]* {adjust();yylval.sval=string(yytext);return ID;}
<INITIAL>"/*" {adjust();count++; BEGIN COMMENT;}
<CONST_STRING>[\"] {adjust(); yylval.sval = string(yytext); BEGIN INITIAL ; return CONSTSTR;}
<CONST_STRING>" " {adjust(); yylval.sval = string(yytext) ; return CONSTSTR;}
<CONST_STRING>. {adjust(); yylval.sval = string(yytext); return CONSTSTR; }
<COMMENT>"*/" {adjust();count--; if(count == 0) BEGIN INITIAL;}
<COMMENT>. {adjust(); }
<COMMENT>\n {adjust(); }
相對於虎書上的定義的一些類型,我又多定義了三個,分別是NUM,CONSTSTR,REAL。所以,必須要修改相應的代碼,添加宏定義,以及現實字符集。
在實現過程中,遇到一些問題,記錄下來:
1.如果在使用包含有c的c++代碼時候 要使用
#ifdef __cplusplus
static int yyinput (void );
#else
static int input (void );
#endif
#ifdef __cplusplus
extern "C" int yywrap (void )
#else
extern int yywrap (void )
#endif
{
charPos = 1 ;
return 1 ;
}
使用以上的方式經行定義,否則將會出現 函數定義不規范的報錯。
2.在vs中使用#include <io.h> ,#include <process.h> 來代替 #include <unistd.h> 后者是在linux中使用的規范
3.還有可能出現 warning:“rule cannot be matched”。這表示你所定義的一個正則表達式並沒有用 ,可能在前面已經定過了,或者使用規則優先原則 ,這個表達式可能不會使用。
