本文是5分鐘理解cfg上下文無關文法的續集,在5分鐘理解cfg上下文無關文法這篇文章中已經講解了cfg的基本概念,但是cfg的解析演算法才是核心。由於它的解析演算法極其複雜,網上很少有文章能把解析演算法用大眾能理解的語言寫出來,本人在理解了演算法後用python**實現了演算法,通過測試用例驗證了該演算法的正確性,當然也驗證了我理解的是正確的。
這裡說的解析其實就是驗證乙個輸入字串input是否符合給定的文法g,實現演算法有cyk解析演算法,和eerley解析演算法,都是是以人名命名。下面介紹eerley解析演算法
假設input的字串是」 2023年2月1日」,驗證是否符合下面定義的日期類文法,下面豎線|表示「或」,dig0_9->zero | dig1_9 實際上等價於2條規則:dig0_9->zero;dig0_9->dig1_9。#後面是注釋。
s->y m d #year month day
y->yn yt #yn:year number;yt:year tag
yt->"年"
m->mn mt #mn:month number; mt:month tag
mn-> dozen #1 到12
mt->"月"
dt->"日"
yn->dig1_9 dig0_9 dig0_9 dig0_9 # dig1_9:digit 1 to 9
dig0_9->zero | dig1_9
dig1_9->one | dig2_9
dig2_9->two | dig3_9
dig3_9-> three | dig4_9
dig4_9->"4" | "5" |"6" |"7" |"8" | "9"
zero->"0"
one->"1"
two->"2"
three->"3"
dozen ->dig1_9 | zero dig1_9 | one zero | one one | one two
d->dn dt
dn-> dig1_9 | zero dig1_9 | one dig1_9 | two dig1_9 | three zero | three one
預備知識:
dot rule: 點規則a->β·α在規則a->b α的基礎上加上乙個點表示目前匹配到的情況,點·的左邊已經正確匹配到input某段字串,·的右邊為待匹配的部分。
命名規範:大寫英文本母(可以多個)表示非終結符(也叫變數)如a,小寫英文本母表示終結符如a,小寫希臘字母表示或者終結符或者非終結符,且可以為空,如α。
假設輸入字串input的長度為n,定義位置號position是每個字元的邊界:
input: a0 a1 a2… a(n-1)
position: 0 1 23 ... n-1 n
上面子字串「a1 a2」起始位置start=1,結束位置end=3
構造乙個n+1行,n+1列的**c,記錄所有子串的匹配狀態。行號表示子串起始位置start的取值,列號表示結束位置。c(1,3)表示第1行,第3列的格仔,裡面的值是子串「a1 a2」(起始位置是1,結束位置是3)的匹配規則及匹配狀態。每個格仔是乙個set,set裡放的是匹配到該子字串的若干條規則,並以dot rule標記。注意set會自動過濾重複的值。比如 a->β·α 放在c(1,3)裡表示,規則中β部分匹配了input中的位置1到位置3的子串「a1 a2」,後面的部分α是否匹配不知道,待後面處理,且α可以是空字串。由於起始位置小於等於結束位置,下面**灰色部分不可能有資料。
演算法步驟
4個主要步驟:初始化,prediction(自頂向下擴充套件變數),scanning(對終結符與輸入字元比較看是否匹配上),completion(自底向上合併,即將低層相鄰的變數或終結符合併成高層的變數)
初始化1:算出輸入字串長度n,構造n+1行n+1列的c**,把所有頂層規則s->·α放在c(0,0),表示規則匹配到0到0的的空字串,·後邊的α等待被進一步處理。
初始化2:構造n+1個先進先出的佇列組成的陣列q,為每一列c**記錄規則的產生和處理的情況,防止重複處理。用元組(規則r,起始位置i,結束位置j)來表示規則r放在c(i,j)格仔中,在每次向c**中新增一條規則時,同時在佇列q中壓入乙個元組(規則,起始位置,結束位置),處理時先彈出規則元組。
在日期類的例子中,頂層規則只有一條,是s->y m d,往c(0,0)放入s->·y m d,並往q中壓入元組(s->·y m d,0,0)
第一層迴圈:for結束位置j=0 to n
第二層迴圈: while(q[j]不為空)
從q[end]中彈出一條規則和位置組成的元組。
if(規則沒有完全匹配某段字串,也就是規則中的點·不在最後):#第一層if
if(規則待處理的是個非終結符或稱為變數): #第二層if
做prediction,即擴充套件變數。具體地:
可以用(a->β·yα,i,j)表示該規則,即·後面待處理的是個非終結符y,β匹配了位置i到j之間的子字串,β和α可以為空,當β空時,i和j相等。遍歷規則集,找到所有擴充套件y的規則 y->γ(可能有多條規則,不同的規則,γ值不同),在c(i,j)中新增規則y->·γ,並在q[j]中壓入(y->·γ,j,j),注意y->·γ匹配的起始點是j不是i,而j表示彈出的規則(a->β·yα,i,j)元組的結束位置。
在日期類例子中,第一次彈出的是(s->·y m d,0,0),即β為空,i=j=0。遍歷規則集可以找到y->yn yt,將y->·yn yt新增到c(0,0)的set中。並將元組(y->·yn yt,0,0)壓入到q[0]中。在反覆迴圈使得變數一層層擴充套件,最終會出現終結符,最終放入c**和壓入q[0]規則可能出現這樣的:one->·」1」, two->·」2」
else if(規則待處理的是個終結符): #對應第二層if
做scanning,即把終結符與輸入相應位置j到j+1之間的字元比較,看是否匹配。如果匹配上,該規則可以用(a->βa(j+1)·α,i,j+1) 表示,其中a(j+1)是input中第j+1個字元,代替了原規則裡的y,點·在原規則的基礎上由β後面移動到a(j+1)後面。將規則a->βa(j+1)·α放入c(i,j+1),並將元組(a->βa(j+1)·α,i,j+1)壓入q[j+1]。
在日期類例子中, two->·」2」 中最靠近·的終結符」2」與位置0到1之間的字元「2」相同則把規則two->」2」· 加入到c(0,1), 把(two->」2」·,0,1)加入到q[1]。而彈出的one->·」1」中終結符「1」,與位置0到1之間的字元「2」不同,則沒匹配上,忽略後面的規則新增和元組壓入佇列的動作。
else if (規則完全匹配某段字串,即規則中的點·在最後): #對應第一層if
該規則可以用(y->α·,i,j)表示,α不為空,α已經匹配了i到j之間的子字串。
做completion,可理解為合併變數。具體地:
在c的第i列中查詢符合a->β·yα特徵的規則,即β已經匹配到從某位置(假設k,kα·左側非終結符相同,都是y。由於a->β·yα已經匹配到k到i的子串,而y已經匹配到i到j的子串,因此合併後就是βy匹配到k到j之間的子串,因此將a->βy·α放入到c(k,j)(a->βy·α,k,j)壓入q[j]
在日期類例子中,當結束位置j增加到2時,從q[2]彈出的元組會出現(zero->」0」·,1,2),這表示zero->」0」的右邊「0」匹配了位置1到位置2的子串,在c的第1列中遍歷,可以在c(0,1)找到規則s->」2」 · zero d0_9 d0_9,其·後面的待處理變數=zero,與zero->」0」·的左邊變數相同,則可以合併得到新的規則元組(s->」2」 zero· d0_9 d0_9,0,2),表示s->」2」 zero· d0_9 d0_9匹配到0到2之間的子串。
退出第二層while迴圈
退出第一層for迴圈
最後檢查c(0,n)是否存在符合s->β·特徵的規則,即左邊是頂層變數s,右側點出現在最後,表示s匹配了0到n之間的子串,即整個input。如果是,則表示驗證成功,否則驗證失敗,字串input不符合文法。
上下文無關文法(CFG)
一 上下文無關文法 g v,sigma,r,s 1 v 有窮變元集 2 sigma 有窮終結符集 3 r 有窮規則集 4 s v 初始變元 二 定理 正則語言都是上下文無關語言。證明方法 可以為每個正則語言設計上下文無關文法。其中cfg的v等於dfa的狀態集 轉移 qi,a qj對應規則 ri ar...
上下文無關文法
1 上下文無關文法又稱cfg。許多cfg由幾個較簡單的cfg合併起來。可以先構造每個部分的cfg,比如 s1,s2,s3.sk。然後加入新的規則s s1 s2 sk 2 例如 構造語言 的cfg,1 構造 s1 0 s1 1 2 s2 1 s2 0 3 整合 s s1 s2 3 如果語言是正則的,可...
上下文無關文法
上下文無關文法 context free gammar 指文法的前後 上下 沒有關係,如文法 statement name is name s friend.是上下文無關文法,因為文中出現的兩個 name 可以不同 也可以相同,假設自己可以是自己的朋友 而文法 statement name like...