expr: expr '+' term | term
def expr():
if expr() and expect('+') and term():
return True
if term():
return True
return False
expr()
以调用expr()
开始,后者也以调用expr()
开始,以此类推……这只能以堆栈溢出而结束,抛出异常RecursionError
。expr: term '+' expr | term
'-'
运算符时(因为a - (b - c)
与(a - b) - c
不一样)。expr: term ('+' term)*
'+'
和'-'
这样的运算符,基本上是二进制的(在 Python 中),当我们解析像a + b + c
这样的东西时,我们必须遍历解析的结果(基本上是列表[‘a’,’+’,‘b’,’+’,‘c’] ),以构造一个左递归的解析树(类似于 [[‘a’,’+’,‘b’] ,’+’,‘c’] )。foo + bar + baz
作为示例。我们想要解析出的解析树对应于(foo + bar)+ baz
。这需要对expr()
进行三次左递归调用:一次对应于顶级的“+” 运算符(即第二个); 一次对应于内部的“+”运算符(即第一个); 还有一次是选择第二个备选项(即term
)。expr------------+------+
| \ \
expr--+------+ '+' term
| \ \ |
expr '+' term |
| | |
term | |
| | |
'foo' 'bar' 'baz'
def expr():
if oracle() and expr() and expect('+') and term():
return True
if term():
return True
return False
sys._getframe()
来实现它,但有更好的方法:让我们反转调用的堆栈!expr()->term()->'foo'
。(它应该返回初始的term
的解析树,即'foo'
。上面的代码仅返回 True,但在本系列第二篇文章中,我已经演示了如何返回一个解析树。)很容易编写一个 oracle 来实现,它应该在首次调用时就返回 false——不需要检查堆栈或向前回看。expr()
,这时 oracle 会返回 true,但是我们不对 expr() 进行左递归调用,而是用前一次调用时保存的结果来替换。瞧呐,预期的'+'
运算符及随后的term
也出现了,所以我们将会得到foo + bar
。oracle_expr()
。代码:def expr():
if oracle_expr() and expect('+') and term():
return True
if term():
return True
return False
oracle_expr()
函数将读取该全局变量,而装饰器操纵着它:saved_result = None
def oracle_expr():
if saved_result is None:
return False
return saved_result
def expr_wrapper():
global saved_result
saved_result = None
parsed_length = 0
while True:
new_result = expr()
if not new_result:
break
new_parsed_length = <calculate size of new_result>
if new_parsed_length <= parsed_length:
break
saved_result = new_result
parsed_length = new_parsed_length
return saved_result
oracle_expr()
函数——我们可以生成对 expr() 的标准调用,无论它是否处于左递归的位置。def is_left_recursive(rule):
for alt in rule.alts:
if alt[0] == rule.name:
return True
return False
def memoize(func):
def memoize_wrapper(self, *args):
pos = self.mark()
memo = self.memos.get(pos)
if memo is None:
memo = self.memos[pos] = {}
key = (func, args)
if key in memo:
res, endpos = memo[key]
self.reset(endpos)
else:
res = func(self, *args)
endpos = self.mark()
memo[key] = res, endpos
return res
return memoize_wrapper
memo
字典。memoize_wrapper 函数的前四行与获取正确的memo
字典有关。 def memoize_left_rec(func):
def memoize_left_rec_wrapper(self, *args):
pos = self.mark()
memo = self.memos.get(pos)
if memo is None:
memo = self.memos[pos] = {}
key = (func, args)
if key in memo:
res, endpos = memo[key]
self.reset(endpos)
else:
# Prime the cache with a failure.
memo[key] = lastres, lastpos = None, pos
# Loop until no longer parse is obtained.
while True:
self.reset(pos)
res = func(self, *args)
endpos = self.mark()
if endpos <= lastpos:
break
memo[key] = lastres, lastpos = res, endpos
res = lastres
self.reset(lastpos)
return res
return memoize_left_rec_wrapper
@memoize_left_rec
def expr(self):
pos = self.mark()
if ((expr := self.expr()) and
self.expect('+') and
(term := self.term())):
return Node('expr', [expr, term])
self.reset(pos)
if term := self.term():
return Node('term', [term])
self.reset(pos)
return None
foo + bar + baz
。 # Prime the cache with a failure.
memo[key] = lastres, lastpos = None, pos
expr := self.expr()
)。所以我们继续到第二个 if,它成功识别了一个 term(在我们的例子中是 ‘foo’),expr 返回一个 Node 实例。它返回到了哪里?到了装饰器里的 while 循环。这新的结果会更新 memo 缓存(那个 node 实例),然后开始下一个迭代。foo + bar
,回到 while 循环,还会经历相同的过程:用新的(更长的)结果来更新 memo 缓存,并开启下一轮迭代。(foo + bar) + baz
,并返回给 while 循环,后者将它填充进 memo 缓存,并再次迭代。