5分钟理解CFG上下文无关文法的解析算法

本文是5分钟理解CFG上下文无关文法的续集,在5分钟理解CFG上下文无关文法这篇文章中已经讲解了CFG的基本概念,但是CFG的解析算法才是核心。由于它的解析算法极其复杂,网上很少有文章能把解析算法用大众能理解的语言写出来,本人在理解了算法后用python代码实现了算法,通过测试用例验证了该算法的正确性,当然也验证了我理解的是正确的。

这里说的解析其实就是验证一个输入字符串input是否符合给定的文法G,实现算法有CYK解析算法,和Eerley解析算法,都是是以人名命名。下面介绍Eerley解析算法

假设input的字符串是” 2021年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

预备知识:

  1. Dot rule: 点规则A->β·α在规则A->B α的基础上加上一个点表示目前匹配到的情况,点·的左边已经正确匹配到input某段字符串,·的右边为待匹配的部分。
  2. 命名规范:大写英文字母(可以多个)表示非终结符(也叫变量)如A,小写英文字母表示终结符如a,小写希腊字母表示或者终结符或者非终结符,且可以为空,如α。
  3. 假设输入字符串input的长度为n,定义位置号position是每个字符的边界:

Input:           a0    a1     a2…            a(n-1)

Position:   0     1      2        3  ...   n-1             n

上面子字符串“a1 a2”起始位置start=1,结束位置end=3

 

  1. 构造一个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. 初始化1:算出输入字符串长度n,构造n+1行n+1列的C表格,把所有顶层规则S->·α放在C(0,0),表示规则匹配到0到0的的空字符串,·后边的α等待被进一步处理。
  2. 初始化2:构造n+1个先进先出的队列组成的数组q[],为每一列C表格记录规则的产生和处理的情况,防止重复处理。用元组(规则r,起始位置i,结束位置j)来表示规则r放在C(i,j)格子中,在每次向C表格中添加一条规则时,同时在队列q中压入一个元组(规则,起始位置,结束位置),处理时先弹出规则元组。
  3. 在日期类的例子中,顶层规则只有一条,是S->Y M D,往C(0,0)放入S->·Y M D,并往q中压入元组(S->·Y M D,0,0)
  4. 第一层循环:for结束位置j=0 to n
  5.   第二层循环: while(q[j]不为空)
  6.    从q[end]中弹出一条规则和位置组成的元组。
  7.    If(规则没有完全匹配某段字串,也就是规则中的点·不在最后):#第一层if
  8.        If(规则待处理的是个非终结符或称为变量):                          #第二层if
  9.             做prediction,即扩展变量。具体地:
  10.             可以用(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)元组的结束位置。
  11.              在日期类例子中,第一次弹出的是(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”
  12.         Else if(规则待处理的是个终结符):                                       #对应第二层if
  13.              做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]。
  14.              在日期类例子中, TWO->·”2” 中最靠近·的终结符”2”与位置0到1之间的字符“2”相同则把规则TWO->”2”· 加入到C(0,1), 把(TWO->”2”·,0,1)加入到q[1]。而弹出的ONE->·”1”中终结符“1”,与位置0到1之间的字符“2”不同,则没匹配上,忽略后面的规则添加和元组压入队列的动作。
  15.     Else if (规则完全匹配某段字串,即规则中的点·在最后):     #对应第一层if
  16.          该规则可以用(Y->α·,i,j)表示,α不为空,α已经匹配了i到j之间的子字符串。
  17.          做completion,可理解为合并变量。具体地:
  18.          在C的第i列中查找符合A->β·Yα特征的规则,即β已经匹配到从某位置(假设k,k<i)到i之间的子字符串,点·后面紧跟着待处理的是个非终结符,且与当前规则Y->α·左侧非终结符相同,都是Y。由于A->β·Yα已经匹配到k到i的子串,而Y已经匹配到i到j的子串,因此合并后就是βY匹配到k到j之间的子串,因此将A->βY·α放入到C(k,j)(A->βY·α,k,j)压入q[j]
  19.          在日期类例子中,当结束位置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之间的子串。
  20.    退出第二层while循环
  21. 退出第一层for循环
  22. 最后检查C(0,n)是否存在符合S->β·特征的规则,即左边是顶层变量S,右侧点出现在最后,表示S匹配了0到n之间的子串,即整个input。如果是,则表示验证成功,否则验证失败,字符串input不符合文法。
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值