[英]Stuck on translating a cfg to dcg
我正在尝试自学序言,并为简单的算术cfg实现解释器:
<expression> --> number
<expression> --> ( <expression> )
<expression> --> <expression> + <expression>
<expression> --> <expression> - <expression>
<expression> --> <expression> * <expression>
<expression> --> <expression> / <expression>
到目前为止,我已经在swi-prolog中编写了此代码,但遇到了以下所述的许多错误。
expression(N) --> number(Cs), { number_codes(N, Cs) }.
expression(N) --> "(", expression(N), ")".
expression(N) --> expression(X), "+", expression(Y), { N is X + Y }.
expression(N) --> expression(X), "-", expression(Y), { N is X - Y }.
number([D|Ds]) --> digit(D), number(Ds).
number([D]) --> digit(D).
digit(D) --> [D], { code_type(D, digit) }.
用测试
phrase(expression(X), "12+4").
给出X = 16,这很好。 也
phrase(expression(X), "(12+4)").
作品和词组(表达式(X),“ 12 + 4 + 5”)。 还可以
但是尝试
phrase(expression(X), "12-4").
导致“错误:超出本地堆栈”,除非我注释掉“ +”规则。 虽然我可以添加两个以上的数字,但括号不能递归工作(即“(1 + 2)+3”挂起)。
我敢肯定解决方案很简单,但是我无法从找到的在线教程中找出解决方案。
您所做的一切原则上都是正确的。 您说对了:答案很简单。
但。
左递归在定句语法中是致命的。 症状恰好是您所看到的行为。
如果在expression
上设置监视点并使用跟踪工具,则可以观察到堆栈不断增长,而解析器根本没有任何进展。
gtrace.
spy(expression).
phrase(expression(N),"12-4").
如果仔细考虑Prolog执行模型,您可以看到正在发生的事情。
我们尝试将“ 12-4”解析为一个表达式。
我们的调用堆栈包含对步骤1中对expression
调用,我将编写expression
(1)。
通过“表达式”的第一个子句,我们成功地将“ 12”解析为表达式,并记录了一个选择点,以备日后需要回溯时使用。 实际上,我们需要立即回溯,因为涉及phrase
的父请求说我们想解析整个字符串,但我们没有:我们还有“ -4”。 因此,我们失败了,回到选择点。 我们已经表明“表达式”的第一个子句不会成功,因此我们尝试对第二个子句进行重试。
调用堆栈: expression
(1)。
我们尝试使用第二个子句为“ expression”解析“ 12-4”,但是立即失败(初始字符不是“(”)。因此,我们失败了,然后针对第三个子句重试。
调用堆栈: expression
(1)。
第三个子句要求我们从输入的开头解析一个表达式,然后找到“ +”和另一个表达式。 因此,我们现在必须尝试将输入的开头解析为表达式。
调用堆栈: expression
(4) expression
(1)。
我们尝试将“ 12-4”的开头解析为一个表达式,然后以“ 12”开头,就像在步骤1中一样。我们记录一个选择点,以备日后需要回溯时使用。
调用堆栈: expression
(4) expression
(1)。
现在,我们恢复从步骤4开始的尝试,以将“ 12-4”解析为针对“表达式”的第3条的表达式。 我们已经完成了第一步。 现在我们必须尝试将“ -4”解析为“ expression”子句3右侧的其余部分,即"+", expression(Y)
。 但是“-”不是“ +”,因此我们立即失败,并返回到最近记录的选择点,即在步骤5中记录的选择点。接下来的事情是尝试找到一种不同的方法来解析起始位置。输入为表达式。 我们使用“表达式”的第二个子句恢复搜索。
调用堆栈: expression
(4) expression
(1)。
第二个子句再次失败。 因此,我们继续“表达式”的第三个子句。 这要求我们在输入的开头查找表达式(作为弄清楚我们当前对“ expression”的两个调用是成功还是失败的一部分)。 因此,我们再次称呼“表达”。
调用堆栈: expression
(7) expression
(4) expression
(1)。
您可以看到,每次我们向堆栈添加对expression
的调用时,我们都会成功,寻找加号,失败,然后再试一次,最终到达第三个子句,这时我们将把另一个调用推入堆栈然后再试一次。
简短的答案:左递归在DCG中是致命的。
在递归下降解析器中它也是致命的,解决方案大体相同:不要向左递归。
语法的非左递归版本为:
expression(N) --> term(N).
expression(N) --> term(X), "+", expression(Y), { N is X + Y }.
expression(N) --> term(X), "-", expression(Y), { N is X - Y }.
term(N) --> number(Cs), { number_codes(N, Cs) }.
term(N) --> "(", expression(N), ")".
但是,这使“-”具有正确的关联性,并且在许多情况下都需要重复重新定义初始术语,因此,用于生产的代码中的常见方法是做的事情不像您开始时使用的BNF,而应像以下EBNF版本那样:
expression = term {("+"|"-") term}
term = number | "(" expression ")".
我学会写它的方式(很久以前,我不再记得要归功于谁了)是这样的(我一开始发现它很丑,但它会在您身上成长):
expression(N) --> term(X), add_op_sequence(X,N).
add_op_sequence(LHS0, Result) -->
"+", term(Y),
{LHS1 is LHS0 + Y},
add_op_sequence(LHS1,Result).
add_op_sequence(LHS0, Result) -->
"-", term(Y),
{LHS1 is LHS0 - Y},
add_op_sequence(LHS1,Result).
add_op_sequence(N,N) --> [].
term(N) --> number(Cs), { number_codes(N, Cs) }.
term(N) --> "(", expression(N), ")".
到目前为止累积的值在add_op_sequence的左侧参数中向下传递,并最终(当序列以空生产结束时)最终向上传递。
称为“左角解析”的解析策略是解决此问题的一种方法。 关于在自然语言处理中使用Prolog的书籍几乎都会讨论它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.