在 上一篇筆記中,咱們談到了爲何須要編譯以及編譯的大體流程。在繼續細講每個流程以前,咱們先經過本篇筆記對一些概念和術語加以瞭解。
字母表也即符號集,用 ∑
表示,它是一個包含各類符號的有窮非空集合。以漢語爲例,漢語字母表就是各類漢字、數字、標點符號的集合;以英語爲例,英語字母表就是各類字母、數字、標點符號的集合......那麼到了編程,字母表就多是字母、數字、各類專用符號和保留字了。git
相關定義:github
符號串是對於字母表來講的一個概念,字母表的符號串指的就是由字母表中各個字符組成的一個有窮序列。編程
注意這裏的「有窮」,指的是符號串自己是由有窮個符號組成,可是符號串的個數是無窮多的(組合方式不一樣)。以字母表 ∑={0,1}
爲例,它的符號串就有:0,1,00,01,10,11,000 等等。閉包
符號串的長度指的是符號串符號的個數,以 m = 000
爲例,|m|= 3
。編程語言
空符號串 ε
長度爲 0,表示不包含任何符號,相似於編程中的空字符串 ""
。因此有 εm = mε= m
。工具
以 m = abc
爲例,它的頭是 ε
,a
,ab
,abc
;它的尾是 ε
,c
,bc
,abc
。而它的固有頭不考慮末尾符號 c
,固有尾不考慮首部符號 a
。學習
鏈接、方冪spa
x = abc
,y = def
,那麼 xy = abcdef
。y = xxxx...xxxx
(n 個 x),那麼就能夠寫成 y = x^n
,此時 y 就是 x 的方冪。這點和數學是同樣的。不過要注意,x^0 ≠ 1 = ε
。以字母表 ∑ = {a,b}
爲例,任何由它的符號串做爲構成元素的集合,均可以稱做字母表的符號串集合。好比說 {ab}
,{abab,ababab}
等。code
兩個符號串集合的乘積定義爲 AB = {xy| x∈A且y∈b}
,其實就是笛卡爾積。blog
通常的字符串集合可能並不能囊括一個字母表的全部符號串,可是有一種集合卻能包含全部的符號串,這種特殊的集合稱爲閉包,記做 ∑*
。*
其實就是全選的意思(聯想 CSS 中的通配選擇符就好理解了)。
∑* = {ε,a,b,ab,ab,ba,aba,aab......} = ∑^0 ∪ ∑^1 ∪ ∑^2 ∪......∪ ∑^n
要注意的是,閉包也包含了空符號串。
將閉包中的空符號串去掉,就成爲了正閉包,也即 ∑+
。顯然:∑*= ∑^0 ∪ ∑+
,∑+ = ∑∑* = ∑*∑
。
語言包括語法和語義兩個方面,可是語法和語義都是比較抽象的東西,因此咱們須要藉助一些工具來闡述它們。以語法來講,文法就是闡述它的一個工具。
文法是描述語言語法結構的形式規則。它的形式化定義是一個四元組,即 G = { VN , VT , P , S }。下面咱們先給出一個天然語言的例子,而後藉此來解釋四元組的各個成分都是什麼。
<句子> → <主語> <謂語>
<主語> → <代詞> | <名詞>
<謂語> → <動詞>
<代詞> → 你 | 我 | 他
<名詞> → 張三 | 教師 | 大學生
<動詞> → 教書 | 學習
(1)VT:
VT 指的是終結符集合。終結符即 terminal symbol
,它是文法所定義的語言的最基本符號,這意味着一個終結符不可再細分(注意「終結」這個詞)。以上面爲例,VT ={ 你,我,他,張三,教師,大學生,教書,學習 }。在編程語言中,終結符其實就是以前提到的 token,好比保留字、運算符、界符等這些最最基本的符號。
終結符通常用小寫字母表示。
(2)VN:
VN 指的是非終結符集合。非終結符即 nonterminal symbol
,它是用來表示語法成分的符號,有時候也稱爲「語法變量」。以上面爲例,VN ={ <句子>,<主語>,<謂語>,<代詞>,<名詞>,<動詞> }。在編程語言中,咱們能夠說表達式或者賦值語句就是一個非終結符,由於它能夠繼續細分爲多個 token。
非終結符的「非終結」,就是說「尚未到盡頭」,還能夠繼續拆分,通常用 <> 括起來。
非終結符通常用大寫字母表示。
PS:終結符和非終結符統稱爲文法符號。
(3)P:
P 即 production
,指的是產生式集合。終結符和非終結符的轉換依靠的就是產生式(或者說生成式,推導規則)。產生式形如 a → β (或者 a : : = β ,這種表示方法即巴科斯範式 ),意思是將 a 定義爲 β。a 稱爲產生式左部,它是終結符集合的一個元素;而 β 稱爲產生式右部,它是終結符和非終結符並集的一個元素。根據前面的定義,很容易就能知道產生式的左部不能是終結符,由於左部都是能夠繼續細分的,可是終結符不能再細分了,而右部在一開始多是非終結符(還沒拆完),但在最後必定會變成終結符(拆完了,不能再拆了)。
以上面爲例,P = { <句子> → <主語> <謂語>,<主語> → <代詞> | <名詞>,<謂語> → <動詞> }
(4)S:
S 即 start symbol
,指的是開始符號(識別符號)。它是最開始的那條產生式的左部,一切的推導都是從它這裏開始進行的,能夠認爲它就是最大的那個成分。因此也註定了 S 必須在 P 中至少做爲某一條產生式的左部(否則無從推導)。
以上面爲例,S = <句子>。
假如如今有文法 G =({S,A,B},{0,1},P,S),
其中,P = { S → 0A,S → 1B,A → 1B,B → 1 }。
是否有更簡便的方法來表示它呢?事實上,這裏僅從產生式集合 P 來看,徹底能夠在不引發歧義的狀況下推斷出終結符號集,非終結符號集以及開始符號。這意味着咱們能夠將這三者省略,僅用產生式集合表達文法自己,也即:
G:
S → 0A
S → 1B
A → 1B
B → 1
B → 0
更進一步地,咱們發現部分產生式的左部都是同樣的,因此能夠繼續簡寫爲:
G:
S → 0A | 1B
A → 1B
B → 1 | 0
此時,0A 或者 1B 稱爲 S 的候選式(candidate),1 或者 0 稱爲 B 的候選式。
(1)直接推導:
假如文法 G = { VN , VT , P , S } 有一條產生式爲 a → β ,γ 和 δ 是 V*= VN ∪ VT 中的任意符號(便是文法中的任意終結符或者非終結符),如有符號串知足:
V = γ a δ ,W = γ β δ
那麼就說 V 能夠直接推導獲得 W,或者說 W 是 V 的直接推導,W 直接規約到 V —— 記做 V ⇒ W。咱們看一個例子:
假如如今有文法 G =({S,A,B},{0,1},P,S),其中,P = { S → 0A,S → 1B,A → 1B,B → 1 }。
那麼以產生式 S → 0A 爲例,咱們是能夠說 S ⇒ 0A 的,由於 S = εSε,0A = ε0Aε(別忘了,空符號串也是屬於 V* 的),以此類推,全部的產生式實際上都是一個直接推導。
(2)推導:
推導指的是從文法的開始符號出發,反覆連續地使用產生式,對非終結符施行替換和展開,最終獲得一個僅由終結符構成的符號串,推導過程的每一步都是一個直接推導。
仍是以上面的文法爲例,那麼就有 S ⇒ 0A ⇒ 01B ⇒ 011,這個序列就是從 S 到 011 的一個推導,或者說 S 能夠推導出 011。
序列能夠簡寫爲 S +⇒ 011,表示通過一步或者多步推導,而 S *⇒ 011 表示通過 0 步或者多步推導。因此,S *⇒ 011 要麼是 S = 011,要麼是 S +⇒ 011。
(3)最左/最右推導:
推導的過程並非惟一的。對於任何一步 α ⇒ β,若是都是對 α 中的最左非終結符進行替換,那麼就說最左推導,反之就是最右推導。
假如給定文法G:E → E + E | E * E | (E) | i
,由該文法最終能夠推導獲得句子 (i * i + i)。若是採用最右推導,那麼過程就是:
E ⇒ (E) ⇒ (E + E) ⇒ (E + i) ⇒ (E * E + i) ⇒ (E * i + i) ⇒ (i * i + i)。
在每一步中,咱們都儘量地替換 α 中的最左非終結符。
咱們能夠藉助語法分析樹(這裏的語法分析樹是具體語法樹,即 parse tree,不是抽象語法樹)這個結構來描述句型的推導。好比給定文法 G:
G = ( {S,A},{a,b},P,S ),其中 P ={ S → aAS,A → SbA,A → SS,S → a,A → ba }
能夠這樣推導出句子 aabbaa:S ⇒ aAS ⇒ aSbAS ⇒ aabAS ⇒ aabbaS ⇒ aabbaa
那麼如何用分析樹表達這個句子呢?如圖所示:
用根節點表明開始符號,隨着推導的進行,當某個非終結符被它的候選式所替換時,這個非終結符的相應結點就會產生下一代子結點,以此類推。
有時候,對於某個句子,因爲它的推導過程不惟一,因此會致使它的分析樹也不惟一。以前的例子中,咱們給定了文法 G:E → E + E | E * E | (E) | i
,由這個文法推導出句子 (i * i + i),實際上有兩種方式:
對應地有兩種分析樹:
因爲這個文法存在着某個句子對應着兩棵不一樣的分析樹,因此這個文法是二義(歧義)的。
顯然,程序語言不能出現歧義。消除歧義的方法之一是改寫語法,但這種改寫很是困難;另外一種方法就是引入 優先級 ,利用符號的優先級來選擇須要的推導方式。
做爲描述程序語言的上下文無關文法,咱們對它還有一些限制:
喬姆斯基把文法劃分爲四種類型(從 0 型到 1型),這四種類型層層加強,越到後面限制越大。
0 型文法也叫短語文法。設 G = { VN , VT , P , S },若是它的每一個產生式 α→β 都知足:
α∈(VN∪VT)* 且至少含有一個非終結符,而 β∈(VN∪VT)*
那麼這種文法就稱爲 0 型文法。其中,VN∪VT 表明的是終結符合集和非終結符號集的並集,注意這一樣是一個字母集,因此外面加上星號,就成爲咱們開篇所說的字母集的閉包。也就是說,產生式的左部或者右部,必須是由終結符和非終結符構成的符號串。
在 0 型文法的基礎上加以限制,規定對於每個 α→β,都必須知足 |α| <= |β|。也就是說,產生式左部符號串長度必須小於等於右部符號串長度。這裏要注意一個特例就是: α → ε,雖然左部長度必定大於右部長度,但它仍然符合 1 型文法。
1 型文法也叫上下文有關文法。
在 1 型文法的基礎上加以限制,規定對於每個 α→β,都必須知足 α 是一個非終結符。也就是說,產生式左部必須得是一個非終結符。
2 型文法也叫上下文無關文法。
在 3 型文法的基礎上加以限制,規定對於每個 α→β,要麼必須知足 A→ α | αB(右線性),要麼必須知足 A→ α | Bα(左線性)。這裏的 AB 表明非終極符號。
3 型文法也叫正規文法。
上下文其實是在替換非終結符的時候給予的一個限制條件。也就是說,若是文法是上下文有關的,那麼進行替換的時候須要考慮上下文,反之則沒必要。比方說,γ a δ → γ β δ 是 1 型文法的一個產生式,γ 和 δ 都不爲空,則非終結符 a 只有在 γ 和 δ 這樣的一個上下文環境裏才能被替換成 β。
下面咱們用更加通俗的例子來解釋這兩種文法:
定義上下文無關文法 G :
Grammar → X Y Z
X → 我 | 學校
Y → 去 | 沒有
Z → 公園 | 人
那麼以 Grammar 做爲開始符號,就會產生各類句子,其中既有像「我去公園」,「學校沒有人」這種句意通順的,也不乏「我沒有人」,「學校去公園」這種狗屁不通的。爲何會產生不符合語義的句子?這是由於咱們沒有給定上下文的約束,也就是說,由於有了 Y → 去 | 沒有
這條產生式,因此只要遇到 Y,推導出「去」或者「沒有」就都是合理的,而全然不須要關注「去「的上文是什麼,」去「的下文是什麼。
可是若是定義上下文有關文法 G‘:
Grammar → X Y Z
X → 我 | 學校
我 Y → 我去
學校 Y → 學校沒有
去 C → 去公園
沒有 C → 沒有人
那麼就徹底不同了。這時候非終結符的替換是受到上下文限制的 ——
Y 只有在上文是」我「 的時候才能被替換成」去「,只有在上文是」學校「 的時候才能被替換成」沒有「,所以不會產生諸如」學校去「或者」我沒有「這樣的句子;同理,C 只有在上文是」去「 的時候才能被替換成」公園「,只有在上文是」沒有「 的時候才能被替換成」人「,所以不會產生諸如」沒有公園「或者」去人「這樣的句子。這樣就保證了產生的句子是符合語義的。
最後咱們再來總結本篇筆記所講的內容。在文章開始,咱們先給出了一些相關術語的概念和形式,這是爲了更好地在後面形式化地表示文法;接着,咱們引入了文法的概念,包括它的形式化定義,它的推導;而後,咱們引入了語法樹的概念,用以描述推導的過程;最後,咱們解釋了文法的幾種類型(0 ~ 3),並經過例子補充了文法在有/無上下文約束的狀況下分別會推導出什麼句型。