确定第一组CFG

时间:2013-09-17 22:30:29

标签: python parsing context-free-grammar

对于LR解析器,FIRST集定义如下(source):

  

FIRST(A)是可以作为第一个元素出现的一组终端   与非终结符A匹配的任何规则链。

现在给出一个CFG,(I)不允许空制作(即没有规则是格式X → ε)和(II)是正确的(即没有符号产生自己),我试图确定第一集。

我的理由是:

  • 由于没有空制作,只需查看每条规则右侧的第一个符号即可。
  • 对于所有规则X → tα(是终端,α是任意符号串),t在FIRST(X)中。
  • 对于所有规则X → Yα(Y为非终端且α为任意符号串),FIRST(Y)的所有元素均为FIRST(X)。

X → Yα的情况一样,我需要FIRST(Y)来确定FIRST(X),我想出了这种递归方法:

class Rule:
    nextId = 0

    def __init__ (self, left, right):
        self.left = left
        self.right = right
        self.id = Rule.nextId
        Rule.nextId += 1

    def __repr__ (self):
        return 'R{}: {} → {}'.format (self.id, self.left, ' '.join (self.right) )

class Grammar:
    def __init__ (self, rules):
        self.rules = {rule.id: rule for rule in rules}
        self.symbols = set (symbol for rule in rules for symbol in [rule.left] + rule.right)
        self.nonTerminals = set (rule.left for rule in rules)
        self.terminals = self.symbols - self.nonTerminals
        self.calculateFirst ()

    def calculateFirst (self):
        self.first = {}
        for nonTerminal in self.nonTerminals:
            self.first [nonTerminal] = self.getFirst (nonTerminal)

    def getFirst (self, symbol):
        if symbol in self.first: return self.first [symbol]

        first = set ()
        for rule in (rule for rule in self.rules.values () if rule.left == symbol):
            prefix = rule.right [0]
            if prefix == symbol: continue
            if prefix in self.terminals: first.add (prefix)
            else: first |= self.getFirst (prefix)

        return first

#here be dragons
rules = [Rule ('S', ['E'] ), Rule ('E', ['T'] ), Rule ('E', ['(', 'E', ')'] ), Rule ('T', ['n'] ), Rule ('T', ['+', 'n'] ), Rule ('T', ['T', '+', 'n'] ) ]


g = Grammar (rules)
print ('Rules:')
for rule in g.rules.values (): print ('\t{}'.format (rule) )
for nonTerminal in g.nonTerminals:
    print ('First ({}) = {}'.format (nonTerminal, g.first [nonTerminal] ) )

对于维基百科上给出的语法,这会产生以下结果:

Rules:
    R0: S → E
    R1: E → T
    R2: E → ( E )
    R3: T → n
    R4: T → + n
    R5: T → T + n
First (S) = {'+', '(', 'n'}
First (E) = {'+', '(', 'n'}
First (T) = {'+', 'n'}

对于另一种语法,它产生:

Rules:
    R0: S → N
    R1: N → V = E
    R2: N → E
    R3: E → V
    R4: V → x
    R5: V → * E
First (V) = {'*', 'x'}
First (S) = {'*', 'x'}
First (N) = {'*', 'x'}
First (E) = {'*', 'x'}

我的问题是:

1。对于符合I和II的任何给定规则,此算法是否会停止。

2。对于符合I和II的任何给定规则,该算法是否实际产生了所有非终端的正确FIRST集。

第3。有一些聪明的方法吗?

我提前感谢您的意见和答案。

Nota bene :我不确定是在这里发布还是代码审查,但由于我不知道我的代码是否有效(即产生预期结果)我决定在此发布。如果你认为它属于CR,请告诉我。

1 个答案:

答案 0 :(得分:2)

我认为你的程序会工作,因为如果没有可以为空的非终端,它会终止并产生正确的答案。 (自引用非终端应该没问题。)

有一种聪明的方法可以做到这一点。总是不存在吗?通常情况下,Robert Tarjan提出了一个聪明的解决方案,尽管还有其他聪明的解决方案。但是,对于大多数语法来说,使用简单的解决方案通常要快得多。

定义关系A directly-starts-with V,其中A是非终端,V是任何符号; A directly-starts-with V如果有一些作品A → V α。对于没有可空的非终端的语法,FIRST(A)只是directly-starts-with关系的传递闭包,范围仅限于终端。 Tarjan的算法(计算图中强关联的组件)可以快速,优雅地解决这个问题。

还有其他用于计算传递闭包的好算法。 Floyd-Warshall是另一种受欢迎的选择。正如你可能猜测的那样,你可以将传递闭包减少到与矩阵乘法非常相似的东西;可用于减少矩阵乘法的时间复杂度的相同技术也适用于传递闭包;然而,它们在现实世界中并不是特别有用。

可以为空的非终端并不会使情况复杂化,只要你能识别它们(这真的很容易)。您只需将directly-starts-with扩展为包含RHS上的任何符号,该符号前面只有可以为空的非终端。用于识别可以为空的非终端的通常算法几乎可以免费提供directly-starts-with