Конечно, если грамматика описывает произвольно длинные входные данные, вы легко можете оказаться в очень глубокой рекурсии. Простой способ избежать этой ловушки - сохранить приоритетную очередь частично развернутых предложений, где ключ имеет длину. Удалите самый короткий и замените каждый нетерминал каждым возможным способом, выбрасывая те, которые теперь являются всеми терминалами, и добавляя остальные обратно в очередь. Возможно, вы также захотите сохранить «уже отправленный» набор, чтобы избежать дублирования. Если грамматика не имеет ничего общего с продукцией epsilon, где форма предложения выводит более короткую строку, тогда этот метод создает все строки, описанные грамматикой, в неубывающем порядке длины. То есть, после того как вы увидели выходные данные длины N, все строки длиной N-1 и короче уже появились.
Так как OP спросил о деталях, вот реализация грамматики выражения. Это упрощается, переписывая PEG как CFG.
import heapq
def run():
g = {
'<expr>': [
['<term>'],
['<term>', '+', '<expr>'],
['<term>', '-', '<expr>'],
],
'<term>': [
['<fact>'],
['<fact>', '*', '<term>'],
['<fact>', '/', '<term>'],
],
'<fact>': [
['1'],
['(', '<expr>', ')']
],
}
gen(g)
def is_terminal(s):
for sym in s:
if sym.startswith('<'):
return False;
return True;
def gen(g, lim = 10000):
q = [(1, ['<expr>'])]
n = 0;
while n < lim:
_, s = heapq.heappop(q)
# print("pop: " + ''.join(s))
a = []
b = s.copy()
while b:
sym = b.pop(0)
if sym.startswith('<'):
for rhs in g[sym]:
s_new = a.copy()
s_new.extend(rhs)
s_new.extend(b)
if is_terminal(s_new):
print(''.join(s_new))
n += 1
else:
# print("push: " + ''.join(s_new))
heapq.heappush(q, (len(s_new), s_new))
break # only generate leftmost derivations
a.append(sym)
run()
Раскомментируйте лишние print()
s, чтобы увидеть активность кучи. Пример вывода:
1
(1)
1*1
1/1
1+1
1-1
((1))
(1*1)
(1/1)
(1)*1
(1)+1
(1)-1
(1)/1
(1+1)
(1-1)
1*(1)
1*1*1
1*1/1
1+(1)
1+1*1
1+1/1
1+1+1
1+1-1
1-(1)
1-1*1
1-1/1
1-1+1
1-1-1
1/(1)
1/1*1
1/1/1
1*1+1
1*1-1
1/1+1
1/1-1
(((1)))
((1*1))
((1/1))
((1))*1
((1))+1
((1))-1
((1))/1
((1)*1)
((1)+1)
((1)-1)
((1)/1)
((1+1))
((1-1))
(1)*(1)
(1)*1*1
(1)*1/1
(1)+(1)
(1)+1*1