(轉載請代表出處 http://www.cnblogs.com/BlackWalnut/p/4467749.html ) html
當咱們寫好一份源代碼,提交給編譯器的時候,這是編譯器對咱們提交代碼進行詞法分析。這個整個編譯過程的第一步。詞法分析器將咱們的提交的代碼看做是一個文本,它工做的目的就是將這個文本中不符合咱們所使用的語言(c++或者java)的單詞(字符串)挑選出來,以及將符合語言的單詞(字符串)進行分類。java
對於第一個功能,挑選不合格字符串,咱們能夠這樣理解。例如,對於c++而言,定義變量名不能使用數字開頭,那麼若是出現 int 1tmp;語句就是非法的。linux
對於第二個功能,咱們一方面要肯定怎麼解讀一個字符串。例如,if32,是將其當作爲if 和 32 ?仍是將其當作if32?另外一方面,咱們要將其讀到的字符串進行分類,例如,if 是內部關鍵字,它是屬於IF類.可是對於int tmp; 中的tmp,它對於語言(c++或者其餘語言)而言,就是一個id,所以它屬於ID,而前面的int,則是INT.再好比對於43,它是一個常量,也是一個NUM,3.14則是一個REAL。c++
那麼對於一段代碼 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.flex
瞭解了詞法分析的做用後,乍一看,詞法分析面對的處理對象彷佛是一個單詞,例如,先讀入一個單詞,判斷這個單詞的類型,再讀入一個,判斷類型,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」。這表示你所定義的一個正則表達式並無用 ,可能在前面已經定過了,或者使用規則優先原則 ,這個表達式可能不會使用。