对于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,请告诉我。
答案 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
。