词法分析(1)---词法分析的有关概念以及转换图
詞法分析(1)---詞法分析的有關概念以及轉換圖
2009-09-26 23:52:03 閱讀(9) 發表評論
詞法分析是編譯的第一個階段,前面簡介中也談到過詞法分析器的任務就是:
字符流------>詞法記號流
這里詞法分析和語法分析會交錯進行,也就是說,詞法分析器不會讀取所有的詞法記號再使用語法分析器來處理,通常情況下,每取一個詞法記號,就送入語法分析器進行分析,圖解:
?
詞法分析器是編譯器中與源程序直接接觸的部分,因此詞法分析器可以做諸如
1). 去掉注釋,自動生成文檔(c#中的///注釋)
2). 提供錯誤位置(可以通過記錄行號來提供),當字符流變成詞法記號流以后,就沒有了行的概念
3). 完成預處理,比如宏定義
1. 詞法記號,詞法單元(lexeme),模式
模式是一種規則
每個詞法單元都有一個特定記號
比如 int a=3,這里 int,a,=,3都是詞法單元,每個詞法單元都屬于某個詞法記號,比如3就是"num"這個詞法記號的一個詞法單元,而模式規定了什么樣的字符串的詞法記號是什么樣的(模式是一種規則)
某一特定模式規定了某個詞法記號下的一類詞法單元,比如:
模式:用字母開頭的包含字母和數字的串
上面模式的詞法記號:id(所有符合上面模式的字符串的記號都是id)
詞法單元:a123 或者 aabc 等
詞法記號舉例(簡稱為記號):
1) 每個的關鍵字都有屬于自己的一個記號,比如關鍵字for,它可以使用記號for;關鍵字int,可以使用記號int
2) 所有的關系運算符只有一個記號,比如 >=,<=都用記號relation
3) 所有的標識符只有一個記號,比如a123,aab使用記號id
4) 所有的常數只有一個記號,比如123,22,32.3,23E10使用記號num
5) 所有的字符串只有一個記號,比如"123","ab1"使用記號literal
在實際的編譯器設計中,詞法記號,一般用一個整形數字表示
詞法記號的屬性:
我們喜歡用<詞法記號, 屬性>這個二元組來描述一個詞法單元,比如,對于源代碼:position := initial + rate * 60
對于詞法單元 +,我們可以使用 <add_op, '+'> 來表示。
有些情況,更加復雜一點,比如對于 position,我們表示是這樣的,<id, 指向符號表中的position元素的指針>,詳細來說應該是這樣的,假定屬性是一個字符串,那么id將指向這樣一個字符串"position\0",我們把存放這個字符串的地方叫做符號表。有些時候,屬性是不必要的,比如 := ,表示賦值,我們可以使用 <assign_op,257> 這樣的表示這個詞法單元,不過這個顯得有些多于,因為assign_op和詞法單元是一對一的,也就是assign_op只對應了:=,所以額外信息(屬性)就顯得多余的了
詞法錯誤:
詞法分析器是很難(有些錯誤還是可以檢測)檢測錯誤的,因為詞法分析器的目的是產生詞法記號流,它沒有能力去分析程序結構,因此無法檢測到和程序結構有關的錯誤,比如:
fi(a == b)
詞法分析器不會找到這個錯誤,它認為 fi 是一個標識符,而不是一個關鍵字,只有在后面的階段中,這個錯誤才會被發現,這是一個與程序結構有關的錯誤
詞法分析器,只能檢測到詞法單元上的問題,比如 12.ab ,作為一個詞法單元,卻不沒有對應的模式,那么就是產生一個錯誤。
2. 正規式:
前面說過模式是一種規則,為了使用,我們需要一種規范的方式來表達模式,這就是正規式
1) 串和語言
字符類(又叫字母表):關于字符的有限集合
串:字符類上字符的有窮序列,串這個概念,具體來說是,某個字符類上的串
串的長度:串中字符的個數,比如串 s = abc ,那么串的長度為3,用|s|表示串的長度
空串:用 ε 表示
語言:某字符類上的串的集合,屬于語言的串,成為語言的句子或字
比如:{abc, a}這就是一個語言,abc和a就是句子。另外空集也是屬于語言
?
?
連接:x是串,y是串,x和y連接,結果就是 xy 這個串。假如 x 是串,x^3為 xxx。對于 x^n (n>=0),x^0 = ε
語言的運算(假定L和M是語言):
1. L U M = {s|s屬于L或者M},例如:
L={1,2} M={3,4} 那么 L U M = {1,2,3,4}
2. LM = {st|s屬于L且t屬于M},例如:
L={a,b} M={1,2} 那么 LM = {a1,a2,b1,b2} ML={1a,1b,2a,2b}
3. L^n = LLL...LLL (n個L),例如:
L={a,b} 那么 L^3 = {aaa,aab,aba,abb,baa,bab,bbb,bba}
注意 n 可以為0,L^0 = {ε}
4. L* = L^0 U L^1 U L^2 U L^3 U ...
L*表示,語言L中,所有的句子(串)以任意數目任意順序組成的句子的集合,包括 ε,例如:
{a,b}* = {ε,a,b,ab,ba,aab,aba,baa,bba,bab,abb,aaa,bbb...}
L*叫做L的閉包
5. L+ = L^1 U L^2 U L^3 U...
L+表示,語言L中,所有的句子(串)以任意數目任意順序組成的句子的集合,但是不包括 ε
L+中的句子和 L*中的句子相比少一個 ε
那么,我們通過上面的知識就可以表示一個標識符了,我們知道一般語言規定標識符是由字母開頭,后接若干個字母或數字,我們可以這樣來表示: L={a-z A-Z} N={0-9},那么標識符就是 L(L U N)*
2) 正規式
正規式又叫正規表達式,正規式是模式得一種規范的表達形式,正規式描述了一個集合,這個集合是由串組成的,其實這個集合就是我們前面說過的語言,不過這里大家喜歡使用正規集這個術語。正規式 r 表示正規集L(r)
正規式的運算:
1. 閉包運算,運算優先級最高,(r)* 表示 (L(r))*
2. 連接運算,運算優先集合低于閉包,(r)(s) 表示 (L(r))(L(s))
3. 或運算,運算優先集合最低,(r) | (s) 表示 (L(r)) U (L(s))
例如:
a | b 表示集合(語言,正規集) {a,b}
(a | b)(a | b) 表示集合(語言,正規集) {aa,ab,ba,bb}
a* 表示由一切a字符組成的集合(語言,正規集),包括 ε
(a | b) 表示由a,b組成的集合(語言,正規集),包括 ε
等價的正規式:(a | b) = (b | a)
正規式的代數性質:
1. r|s = s|r2. r|(s|t) = (r|s)|t3. (rs)t = r(st)4. r(s|t) = rs|rt5. εr = r6. r** = r*7. r* = (r|ε)*
注意,rs != sr 因為連接運算是有順序的,記住并理解2個最基本的運算:a|b表示{a,b},ab表示{ab}
3. 正規定義
我們可以使用 名字 -> 正規式這種表示,來說明一個等價的代替,比如:
dight -> 0|1|2|3|4|5|6|7|8|9
這里,我們就可以使用名字 digit 來代替后面的正規表達式
我們可以對某個串集進行正規定義,比如我們對標識符集合進行正規定義:
letter -> A|B|...|Z|a|b|...|zdight -> 0|1|2|3|4|5|6|7|8|9id -> letter(letter|dight)*
請通過上面的例子理解正規定義。
在我們表達正規表達式的時候,可以使用一些符號使得表達簡化
1) + ,表示一個或者多個實力,比如,a+ 表示 {a,aa,aaa,aaaa,...}。區別一下*,他們的關系是這里 r+ = r* | ε
2) 字符組,[abc]表示a|b|c,還可以這樣表示[a-zA-Z]表示字母表中的字符
4. 狀態轉換圖
狀態轉換圖是對詞法分析器進行分析過程的描述,我們看一個判斷關系運算的狀態轉化圖:
?
1) 圖中圓圈表示狀態
2) 箭頭叫做邊。X狀態的邊,一般指的是由X狀態出發,指向其他狀態的邊
3) 邊上的符號叫做標記
如何來使用這個圖?假定輸入字符串是 <= ,那么識別開始時,發現 < 和狀態0與狀態1間的邊上的標記一樣,那么就進入1狀態,下一個輸入字符為=,將進入2狀態,識別結束,返回二元組<relop,LE>
上圖中2,3,4,5,7,8狀態,他們表示識別了一個關系運算符,這個狀態叫做接受狀態
狀態4上面有一個*,表示說,輸入指針需要回移。所謂的輸入指針,就是指向輸入字符串中現在被讀入的字符的位置,4狀態會多讀取一個字符,所以需要回移,也就是要注意的是,識別完成之后,輸入指針指向的是被識別對象的最后一個字符,而不是待識別對象的第一個字符,這樣的規定在實現詞法分析器時,是有一定的意義,舉例說明:
輸入字符串為: a>b
識別的時候,從>開始,讀入下一個字符b時,進入4狀態,這個時候,輸入指針指向b,這時候需要回移
我們在需要回移的狀態上加一個*
每個狀態后面有一個return(relop,XX)這個是狀態的行為,這里具體來說就是返回一個二元組的行為,詞法分析器分析的結果就是得到二元組(詞法記號和屬性的二元組),這個二元組可以表示一個特定的字符串。其實上面的*,也是表示行為,也就是輸入指針回移的行為,我們可以看見,只有在接受狀態才會有行為出現
對一門典型的語言來說狀態可能有幾百個
5. 如何編寫一個詞法分析器
1) 根據需要寫出正規定義
2) 根據正規定義畫出轉換圖
3) 根據轉換圖寫出詞法分析器
這里詳細討論面向過程的語言來實現一個詞法分析器(比如c語言),并且主要討論的是第3步
1) 我們需要一個 nextchar() 函數,取得緩存中下一個等待分析的字符,這個函數完成年2個任務
1. 讓輸入指針向前移動一位
2. 返回輸入指針指向的字符
2) 定義一個變量 token_beginning,在每個狀態轉換圖開始的時候,記錄輸入指針的位置,定義forward變量作為輸入指針
3) 狀態轉換圖被實現成為代碼之后,每個狀態都有屬于自己的一塊代碼,這些代碼按順序完成以下工作:
1. 讀取一個字符,通過nextchar()函數
2. 讀取的字符(標志),如果它和當前狀態的邊上的標記相同,那么狀態將轉換到邊所指向的狀態,具體實現只需要一個語句就是 state = xxx(xxx為目標狀態);如果當前狀態的所有邊的標記和這個讀取字符不一樣,那么表示沒有找到token(詞法記號),這時候需要調用 fail() 函數
3. fail() 函數完成這樣的功能:a.指針回移,完成 forward = token_beginning 的操作 b.找到適當的開始狀態(也就是尋找另外一個轉換圖的開始狀態)。假定所有的轉換圖都被嘗試過,并且無法匹配,這時候會調用一個發現錯誤的小程序,來報告錯誤
4. 請不要隨意添加行為到各個狀態所持有的代碼中,應該以轉換圖中表示的行為為準
4) 定義一個全局變量 lexical_value,用于保存一個指針,這個指針由 install_id() 和 install_num() 兩個函數中的一個返回
5) 定義兩個整形變量 start,state,分別表示一個轉換圖的開始狀態和當前的狀態
6) nexttoken(),這是詞法分析器的主程序,可以說,我們通過調用nexttoken()就完成了詞法分析,這個函數一定是這樣的格式:
while(1){ switch(state){ case xx: ... case yy: ... default: ... }}
關于詳細的設計這里就不說了,舉例說明一個轉換圖如何轉換成為程序:
?
這是一個識別浮點數的例子,看下面的代碼:
#include <stdio.h>#include <ctype.h>#include <string.h>char *nexttoken();char nextchar();void next();void back();char* gettoken();char cbuf[]="12.3*********klj12.2e2jj778";int forward = -1; int main(){ while(1){ printf("%s\n",nexttoken()); if(forward >= strlen(cbuf)-1){ getchar(); return 0; } }}int state;int start;char* nexttoken(){ char c; state = 12; while(1){ switch(state){ case 12: c = nextchar(); start = forward; if(isdigit(c)){ state = 13; }else{ next(); } break; case 13: c = nextchar(); if(isdigit(c)) state = 13; else if(c == 'e'||c == 'E') state = 16; else if(c == '.') state = 14; else state = 19; break; case 14: c = nextchar(); if(isdigit(c)) state = 15; break; case 15: c = nextchar(); if(isdigit(c)) state = 15; else if(c == 'e'|| c == 'E') state = 16; else state = 19; break; case 16: c = nextchar(); if(isdigit(c)) state = 18; else if(c == '+' || c == '-') state = 17; break; case 17: c = nextchar(); if(isdigit(c)) state = 18; break; case 18: c = nextchar(); if(isdigit(c)) state = 18; else state = 19; break; case 19: back(); return gettoken(); } }}char nextchar(){ forward ++; return cbuf[forward];}void back(){ forward --;}void next(){ forward ++;}char token_buf[128];char* gettoken(){ int i,j=0; for(i = start; i <= forward; i ++){ token_buf[j++] = cbuf[i]; } token_buf[j] = '\0'; return token_buf;}
?
?
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的词法分析(1)---词法分析的有关概念以及转换图的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最近打算再写一个 局域网聊天软件
- 下一篇: 程序员兄弟姐妹们 飞秋 回来时逢春节