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