【发布时间】:2020-01-24 00:11:19
【问题描述】:
我写了一个 PEG 解析器生成器只是为了好玩(我会在 NPM 上发布它),并认为在它上面添加一个随机短语生成器会很容易。这个想法是在给定语法的情况下自动获得正确的短语。所以我设置了以下规则来从每种类型的解析器生成字符串:
- 序列
p1 p2 ... pn:为每个子解析器生成一个短语并返回连接。 - 替代
p1 | p2 | ... | pn:随机选择一个子解析器并用它生成一个短语。 - 重复
p{n, m}:在[n, m]中选择一个数字x(或[n, n+2]是m === Infinity)并返回x从p生成的短语的串联。 - 终端:只返回终端文字。
当我采用以下语法时:
S: NP VP
PP: P NP
NP: Det N | Det N PP | 'I'
VP: V NP | VP PP
V: 'shot' | 'killed' | 'wounded'
Det: 'an' | 'my'
N: 'elephant' | 'pajamas' | 'cat' | 'dog'
P: 'in' | 'outside'
效果很好。一些例子:
my pajamas killed my elephant
an pajamas wounded my pajamas in my pajamas
an dog in I wounded my cat in I outside my elephant in my elephant in an pajamas outside an cat
I wounded my pajamas in my dog
这个语法有一个递归(PP: P NP > NP: Det N PP)。当我采用其他递归语法时,这次是数学表达式:
expr: term (('+' | '-') term)*
term: fact (('*' | '/') fact)*
fact: '1' | '(' expr ')'
几乎有两次,我收到 “超出最大调用堆栈大小” 错误(在 NodeJS 中)。另一半的时间,我得到了正确的表达:
( 1 ) * 1 + 1
( ( 1 ) / ( 1 + 1 ) - ( 1 / ( 1 * 1 ) ) / ( 1 / 1 - 1 ) ) * 1
( ( ( 1 ) ) )
1
1 / 1
我猜fact 的递归产生式被调用太频繁了,在调用堆栈中太深了,这让整个事情都崩溃了。
我怎样才能让我的方法不那么天真,以避免那些爆炸调用堆栈的情况?谢谢。
【问题讨论】:
-
请注意,使用 PEG,“替代...:随机选择一个子解析器并用它生成一个短语”不一定会产生有效的输入。这是因为 PEG 替代品是有序的,早期替代品可能会遮蔽后来替代品的前缀子集。换句话说,您可以随机选择由子解析器生成的短语,该短语以可能由早期子解析器生成的内容开头,因此解析器永远无法识别。我认为重复存在相关问题。
-
@rici 是的,有没有办法解决这个问题?
-
简单的方法是在返回之前验证生成的句子是否可以被解析。根据您的语法,这可能需要也可能不需要大量重试;我什至不会冒险猜测。除此之外,我不知道;在我看来,几乎所有关于 PEG 的有趣理论问题都被证明是难以解决的,而我从未努力克服这一点。