antlr指南 第二章 編譯原理基礎知識

Antlr指南(第二章 編譯原理基礎知識)

第二章 編譯原理基礎知識

編譯是將計算機高級語言如C++、Java、C#編寫的源程序翻譯成可以在計算機上執行的機器語言的翻譯過程。編譯過程中分:詞法分析、語法分析、語義分析、源代碼優化、代碼生成和目標代碼優化幾個過程。ANTLR解決的是詞法分析和語法分析的問題,下面介紹一下編譯原理中有關詞法分析和語法分析的基本知識。

詞法分析

語法分析

語義分析

源代碼優化

代碼生成

語法樹

註釋樹

記號

代碼生成

目標代碼優化

 

目標代碼

目標代碼

圖2.1

源程序

字符串

詞法分析是對源程序一個一個字符地讀取,從字符中識別出標識符、關鍵字、常量等相對獨立的記號(token,也叫符號或單詞),形成記號序列記號流的過程。如c、l、a、s、s 五個字符構成了關鍵字class,2、3構成了一個整型數23。詞法分析過程中會濾掉源程序中的空格、換行符和註釋等不屬於源程序的字符,還可以將記號歸類,哪些記號屬於標識符,哪些記號屬於關鍵字、整數、浮點數等。記號流是語法分析的基礎。

語法分析是根據詞法分析輸出的記號流,分析源程序的語法結構,並添加代表語法結構的抽象單詞(如:表達式、類、方法等),按照語法結構生成語法樹的過程。前面講的詞法分析後形成的記號序列是描述程序的直接標識符序列,是線性的。它沒有反映出源程序的結構。而語法分析後生成的語法樹是可以表示源程序結構的數據結構,語法樹的葉子節點就是記號。下面舉一個簡單的例子說明詞法分析和語法分析之關的系統,有如下的源程序:

class T {

  string Name;// name of T

  object GetValue() {

}

}

進行詞法分析後形成記號流:

class T { string Name ; object GetValue ( ) { } }。

進行語法分析後形成語法樹:

 

 

 

 

 

 

 

類名

屬性

方法

T

屬性名

名名句

方法名

類型

名名句

返回值

Name

string

GetValue

object

 

我們這裏不介紹編譯原理的其它部分,因爲ANTLR只涉及到了詞法分析和語法分析這兩個部分。讀者可以去參考原理的書籍。瞭解ANTLR在編譯過程中所處的位置後。我們來詳細學習一下有關詞法分析和語法分析的基礎概念。

 

2.1什麼是文法

一種程序設計語言的語法是規定源程序的寫法是否合法的規則,它存在於詞法分析和語法分析兩個階段。如:詞法分析中 123表示合法整數,1_23是不合法整數。在語法分析中if(boolVar) {}是合法的語句,if(boolVar) { 是不合法的語句。那麼我們怎樣來定義語法規則呢?定義語法規則的工具是文法(grammar),文法是由若干定義語法規則的推導式組成的。下面例子中用文法定義了人類語言的語法規則:

語言 =>(句子)+

句子 => 主語 謂語

謂語 => 動詞 賓語

    主語 => 名詞

賓語 => 名詞

名詞 =>‘張三’| ‘代碼’

動詞 =>‘編寫’

 

    如,‘張三編寫代碼’這句話在文法中的推導過程是:

語言 => 主語謂語

         => 張三 動詞 賓語

         => 張三 編寫 名詞

         => 張三 編寫 代碼

另外編譯原理中一般用大寫字母表示一個文法的名稱,再加上文法的啓始規則組成文法的表示符號。如上面的文法如果名稱爲G,可以表示爲G[語言]文法。

 

2.2符號表、符號串、推導式和句子

不管是人類語言還是計算機語言都是用符號組成的,英文由字母、數字和標點符號等組成,中文由漢字、數字和標點符等組成,計算機語言由關鍵字、字母、數字和一些專用符號組成。

這些組成語言的基本符號加上推導出基本符號的抽象符號集合在一起稱爲符號表,用V來表示,符號表是不允許爲空的。如G[語言]文法的符號表是:{語言,句子,主語,謂語,賓語,名詞, 動詞,‘張三’,‘代碼’,‘編寫’},符號表中可以繼續推導的中間符號稱爲非終結符,用Vn表示,不能再繼續推導的符號稱爲終結符,用Vt表示。G[語言]文法的非終結符集合爲:{語言,句子,主語,謂語,賓語,名詞, 動詞},終結符集合爲{‘張三’,‘代碼’,‘編寫’}。

符號表中符號的任意有窮組合序列稱爲符號串。‘張三張三’、‘張三代碼編寫’、‘張三語言句子賓語賓語’都是G[語言]文法符號串。很明顯一種文法的符號串不一定是這種文法的合法句子。符號串是有長度的,它的長度是符號的個數,如‘張三張三’的長度是2,張三語言句子賓語賓語’的長度是5。

文法是定義語法規則的工具,語法規則簡稱規則(rule)又稱推導式或產生式。假設a和b都是一個文法的符號串,我們用a => b表示一個規則,其中a不能爲空。也就是說“句子 =>”是合法的規則“ => 主語”是不合法的,一個文法要由至少要有一個規則。規則a => b使用b來替換a的過程叫做推導,反用b來替換a的過程叫歸約

如G[S]是一個文法,S爲啓始規則,從S推導若干次後形成的符號串叫做G[S]文法的句型。如果推導出的符號串全都由終結符組成此符號串叫做G[S]的句子。前面示例中“張三 動詞賓語”是G[語言]文法的句型,而“張三 編寫 代碼”是G[語言]文法的句子。編譯原理中也使用四元組來表示文法G[Vn,Vt,P,S],其中G爲文法句稱,Vn爲非終結符的集合,Vt爲終結符的集合,P是文法規則的集合,S爲啓始規則。

 

2.3文法的類型

一個文法G[S],S爲啓始規則,如果它的所有規則符合形如:a => b 其中a和b都是G[S]文法的符號串,但a中至少要有一個非終結符,這時G[S]文法是短語文法。G[語言]爲例“賓語張三 => 名詞張三”是短語文法的規則,“張三編寫=> 名詞張三”則不是短語文法,因爲“張三”和“編寫”都是終結符規則左則沒有非終結符。我們可以看出短語文法是對規則做了一些限制後形成的,下面的文法是對短語文法做進一步限制形成的。

如果G[S]的所有規則都滿足形如:a => b其中a的長度要小於等於b,這時G[S]文法是上下文有關文法(context-free grammars)。上下文有關文法的更形象的定義是:文法的所有規則滿足aBc => abc的形式,其中B是非終結符,a、b、c是符號串。也就是說B => b只在前面有a後面有c的情況下才能推導,所以是上下文有關的。例如:“張三動詞程序 => 張三編寫程序”是上下文有關文法的規則。

如果G[S] 的所有規則都滿足形如:a => b其中a是一個非終結符,b是符號串,這時G[S]文法是上下文無關文法(context-sensitive grammars)。就是說a推導出b與其前後是什麼符號串無關。上面G[語言]的文法就是上下文無關文法。

如果G[S] 的所有規則都滿足形如:A=> aB或A=>a其中A和B是非終結符,a是終結符,這時G[S]文法是正規文法(regular grammars)。就是說規則的右則要以終結符開頭。如:“謂語 => 編寫 賓語”,“動詞 => 編寫” 都是正規文法的規則簡稱正規式,“謂語 => 動詞賓語” 就不是正規式。

這四種文法是對規則的限制逐步加強形成的。正規文法是上下文無關文法的特例,上下文無關文法是上下文有關文法的特例,上下文有關文法是短語文法的的特例。文法產生的語言就是該文法的語言,如:上下文無關文法產生的語言就是上下文無關語言,正規文法產生的語言就是正規語言。文法是語言模型。計算機語言中普遍採用上下文無關文法來定義語法規則。下面我們介紹上下文無關文法的語法樹。

短語文法

上下文有

關文法

上下文無

關文法

正規文法


圖2.2

2.4語法樹

編譯技術中用語法樹來更直觀的表示一個句型的推導過程。前面我們已經提到過語法樹,相信讀者已經對語法樹有了一定的認識,這裏我們給出上下文無關文法語法樹的定義:給定上下文無關文法G[S],它的語法樹的每一個節點都有一個G[S]文法的符號與之對應。S爲語法樹的根節點。如果一個節點有子節點。則這個節點對應的符號一定是非終結符。如果一個節點對應的符號爲A,它的子節點對應的符號分別爲A1,A2,A3…..Ak,那麼G[S]文法中一定有一個規則爲:A=>A1 A2 A3 …..Ak。滿足這些規定的樹語法樹也叫推導樹。下面給出一下文法K[S2]和K[S2]文法的一個語型,我們用語法樹來顯示這個語型的推導過程。

K[S2]文法: S2 => aA

A=> bABc

A=>a

B=>d

S2

A

a

b

A

B

a

d

c


K[S2]文法對於語型abadc的推導樹爲:

推導的過程中優先選擇不同的規則進行推導會使推導過程有所不同。下面舉一個例子。

K[S3]文法:S3 => ABD

A=>a

B=>bC

C=>c

D=>d

下面是對於句型abcd的三種不同推導過程。

① S3=> ABD => aBD => abCD => abcD => abcd

② S3=> ABD =>AbCD=>AbcD=>abcD=>abcd

S3

A

B

D

a

b

C

c

d


③ S3=> ABD =>ABd=>AbCd=>Abcd=>abcd

我們可以注意到①過程中所有推導都是選擇的最左邊的非終結符進行替換。③過程中所有推導都是選擇的最右邊的非終結符進行替換。其中①被稱爲最左推導,③被稱爲最右推導。這三種推導都對應一棵語法樹,這說明語法樹反應了此句型的所有推導過程。

但是對於有些句型來說,它對應的語法樹不一定唯一的。也不是說一棵語法樹不一定能反應一個句型的所有推導過程,如下面給定文法。

S4[E]文法:

E => E + E

E => E * E

E => (E)

E => i

對於 i + i * i 句型,我們可以寫出下面兩種最左推導的過程:

① E => E + E => E + E * E => i + E * E => i + i * E

② E => E * E => E + E * E => i + E * E => i + i * E

①過程中第一步使用了E => E + E規則,②過程中第一步使用了E => E * E規則,不管選擇哪個規則都是最左推導。下面有兩棵語法樹與之對應。對於一個文法的句型如果有多於一棵的語法樹與之對應,則這個文法是有二義性的文法。也可以用另一種方法判斷,如果一個文法的最左或最右推導的過程是不唯一的也可以說這個文法是有二義性的文法。

推導②的語法樹

推導①的語法樹

E

E

E

E

E

+

*

E

E

E

E

E

*

+

i

i

i

i

i

i


二義性文法是在開發語法分析器時需要解決的問題,我們將S4[E]加入操作符優先關係改成下面形式可以去掉文法的二義性。

E

T

T

F

F

+

*

 

i

i

i

F

S5[E]文法:

E => T + T

E => T

T => F * F

T => F

F => (E)

F => i

使用S5[E]文法對於 i + i * i 句型的推導過程和語法樹是唯一的:

E => T + T => T + F * F => F + F * F => i + F * F => i + i * F => i + i * i

由於文法簡單所以二義性比較容易解決,但是當文法很複雜的時候,檢查文法中是否存在二義性就困難了。但ANTLR的開發者不用擔心,ANTLR會象我們編譯普通源程序那樣提示文法中的問題,其中包括文法的二義性問題,這使我們可以很容易的找到存在二義性的規則。

 

2.5分析方法

前面講到了句型的推導過程和生成語法樹的過程,有了語法樹就已經很清晰的看到了句型的結構,我們可以很容易的從語法樹中獲得我們相要的信息,這個過程就是語法分析。如圖2.3顯示了對於SELECT F1, F2 FROM Table1 WHERE F1=”a”的語句進了語法分析後生成的語法樹,利用非終結符節點SeletctList很容易對應Select語句的F1,F2部分。

SeletctStatement

SeletctList

SELECT

FROM

SeletctItem

SeletctItem

F1

F1

TableSource

Table1

WhereCondition

WHERE

Expr

F1

=

“a”


圖2.3

我們前面的推導是靠自己主觀判斷,選擇適當的規則進行推導的。那麼如何用程序來實現這個過程呢?語法分析方法分兩大類:自頂向下的分析方法自下而上的分析方法。ANTLR使用的是自頂向下的分析方法。自頂向下的分析方法的思路是從起始規則開始選擇適當的規則反覆推導,直到推導出待分析的語型。如果推導失敗則反回選擇其它規則進行推導(這個過程叫做回朔(backtrack)),如果所有規則都失敗說明這個句型是非法的。下面舉一個分析的示例。

D1[S]文法:

S => aBd

B => b

B = > bc

對於abcd句型進行自頂向下分析,第一步唯一的選擇規則S => aBd,第二步對非終結符B的推導,先選擇B => b推導出S => abd這和句型abcd不同所以推導失敗。現在返回到對B的推導,選擇另一個規則B => bc行出S => abcd這次推導成功。

S

 

 

 

S

S

a

B

d

B

c

b

a

d

自下而上的分析方法與自頂向下分析方法相反,過程是逐個的掃描句型的符號使用適當的規則進行反覆歸約,直到歸約成啓始規則S。如果這個過程失敗,則返回選擇其它規則進行歸約。我們使用自下而上的分析方法對D1[S]示例進行分析。首先是掃描到了第一個符號a,a無法歸約沒有象X => a這樣的規則。然後繼續掃描符號b,b可以用B = > b來歸約得出aB。然後掃描到符號c,這時aBc不能繼續進行歸約造成過程失敗。所以要返回前一步使用B => bc來歸約得出aBd,aBd可以用S => aBd歸約到S。

 

 

a

B

B

c

b

a

d

c

b

a

b

c

S

不管是自下而上的分析方法還是自頂向下分析方法如果選擇的規則不正確,就要返回重新嘗試用其它規則進行推導或歸約。這在實際操作中會浪費很多時間分析程序的執行效率會降低。爲了解決這個問題,在編譯技術中使用一種向前探測符號的方法(lookahead)保證可以正確選擇規則。如D1[S]示例的自頂向下分析的第二步如果選擇B => b則得出ab句型後面的符號爲c,如果選擇B => b規則推導將得出abd,所以不能選擇B => b規則。如果選擇B => bc可以得出abc和後面的符號d相符,所以應該選擇B => bc規則。

在自下而上的分析方法中讀取前兩個符號ab時b可以用規則B=>b歸約,這時向前探測一符號爲c可以得出aBc,但aBc沒有規則可以歸約。所以再讀取一個符號c符,選擇B=>bc規則歸約。向前探測一符號爲d,aBd可以規約成S分析成功。

 

2.6有害規則

在文法可能會出現一些無用的、造成文法二義性的規則。如左右兩側相同的規則A =>A,這種規則在文法中沒有意義,如果還有一條規則S => A,當我們用A歸約時A=>A會干擾使分析器不知道應該用哪一個規則歸約同,如果不斷使A => A歸約會造成死循環。如果一個非終結符不出現在任何規則的右部,那麼這個非終結符是不可達的,也就是說沒有句型在推導或歸約過過程中會用到這個非終結符。如一個文法中有規則 A => a但是沒有形如X => A的規則那麼A=>a在文法中是多餘的。還有一種叫做不可終止的非終結符,如一個文法中對於A非終結符來說只有A => Aa這個規則,可以看出A無法推導出一個句子它也是多餘的。這些規則應該在文法中刪除。

 

 

2.7左遞歸、右遞歸

形如A => Ab的規則,A的定義是遞歸的可以推導出Abbbb…b,左側的非終結符A可以不斷地推導出Ab,這種處於規則左側的遞歸叫左遞歸。遞歸也可能出現在多個非終結符之間A=>Bd,B=>Bc這裏的A=>Bd也是左遞歸。例如我們要定義一個整型數其規則爲:INT => INT Digital,Digital => 0|1|2|3|4|5|6|7|8|9,規則INT用左遞歸實現了多位整型數的定義。相反形如A => bA的規則,A的定義也是遞歸的但和左遞歸相反非終結符A在規則的右側這樣遞歸叫做右遞歸。我們可以把整型數定義的規則用右遞歸的方法定義爲INT => Digital INT,Digital => 0|1|2|3|4|5|6|7|8|9。使用這兩種遞歸的方法時,要看語法分析程序的分析方式,如果語法分析程序是從左向右分析的,那麼使用右遞歸比較適合,反之使用左遞歸比較適合。

 

2.8文法定義基礎

ANTLR的文法定義使用了類似EBNF(Extended Backus-Naur Form)的定義方式,是一種強大簡潔的文法定義方式。本章前面的文法定義的寫法比較繁瑣,定義複雜的文法時非常不便,文法的可讀性也會較差。ANTLR的文法定義方式形象直觀,可以用很短的行數描述以前要很多行才能表示的文法內容。

規則的表示:文法是由規則組成的,本章前面的規則都是用A=>a形式來表示的。ANTLR用A : a;來表示規則,“:”代替了“=>”。ANTLR的規則要以分號“;”結束。我們可以方便地稱規則A : a爲規則A。在規則中有幾種運算關係,選擇、連接、重複、可選。

連接“ ”:規則A : a b c; a、b、c之間用空格分隔。此規則接收句型abc,符號a、b、c是按順序連接起來的關係。

選擇“| ”:規則A : a | b | c; “|”表示“或”的關係,符號A可以推導出a或b或c,也就是在a、b、c中選擇。這要比寫成A : a; A : b; A : c;方便得多。連接和選擇可以聯合起來使用,如A : a b c | c d e;。有進也會使句型的數量增多如:A : B D; B : a | b; D : c | d;這時符號A推導出的句型有 ac、ad、bc、bd四種。

重複“*,+”:定義符號的多重性,規則A : a*; “*”表示a可以出現0次或多次。A : a*; 相當於A : A a | ;。這樣可以避免遞歸的定義,可文法定義中遞歸往往引起文法的二義性。如果a至少要出現一次可以表示爲A : a+; “+”表示a可以出現1次或多次。相當於A : A a | a;。重複可以和連接、選擇一起使用如:A : a * b | c + d;。

可選“?”:規則A : a?; “?”表示a可以出現0次或1次,即a可有可無。相當於A : a | ;。可選可以和連接、選擇、重複一起使用如:A : a* b? | c+ d?;。

子規則“()”:規則A : (a b) | b; a與b在括號中,這樣“(a b)”形成了一個子規則,也就是說可以把規則寫成A : B | b; B : a b;兩個規則表示,我們把B規則用括號括起來放到A規則中這樣就是A規則的子規則了。利用子規則也可以把多個符一起進行描述,A : (a b c)* 規則中a、b、c三個符號可以一起重複0次或多次。子規則有利於我們把很複雜的多個規則寫到一起,有時這樣寫會使文法既簡練又直觀。子規則和前面的各種特性用到一起可以把複雜的文法寫的很濃縮。如:A : (a b c)* | (c d)+ e?;。

值得注意的是如果我們的規則中有“()”的字符該如何表示?因爲子規則也是用“()”表示的。在ANTLR中表示字符要用“’”單引號括起來,用‘(’ ‘)’來表示括號字符。前面講到的表示文法規則的符號“| * + () ?”叫做文法的元符號。

註釋“//  /**/”:和一般編程語言一樣,ANTLR在文法定義中也可以添加註釋。用//來添加單行註釋,如規則E : ‘(’ E ‘)’ | INT // E表示算術表達式。用/*…*/添加多行註釋,與C++相同。

 

2.9本章小結

本章學習了編譯原理的基礎知識。包括:什麼叫詞法分析和語法分析,ANTLR在編譯技術中所處的位置。什麼叫文法,規則。什麼叫短語文法,上下有關文法,上下文法無關文法,正規文法。語法樹,句型的最左推導最右推導和文法的二義性,自頂向下的分析方法和自下而上的分析方法。ANTLR的文法定義方法。

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