Parsing 在Prolog中解析表达式并返回抽象语法

Parsing 在Prolog中解析表达式并返回抽象语法,parsing,prolog,dcg,failure-slice,Parsing,Prolog,Dcg,Failure Slice,我必须编写parse(Tkns,T),它以标记列表的形式接受一个数学表达式并找到T,然后返回一个表示抽象语法的语句,这与操作顺序和关联性有关 比如说, ?- parse( [ num(3), plus, num(2), star, num(1) ], T ). T = add(integer(3), multiply(integer(2), integer(1))) ; No 我尝试实现+和*如下 parse([num(X)], integer(X)). parse(Tkns, T) :-

我必须编写parse(Tkns,T),它以标记列表的形式接受一个数学表达式并找到T,然后返回一个表示抽象语法的语句,这与操作顺序和关联性有关

比如说,

?- parse( [ num(3), plus, num(2), star, num(1) ], T ).

T = add(integer(3), multiply(integer(2), integer(1))) ;
No
我尝试实现+和*如下

parse([num(X)], integer(X)).
parse(Tkns, T) :-
  (  append(E1, [plus|E2], Tkns),
     parse(E1, T1),
     parse(E2, T2),
     T = add(T1,T2)
  ;  append(E1, [star|E2], Tkns),
     parse(E1, T1),
     parse(E2, T2),
     T = multiply(T1,T2)
  ).
它查找正确答案,但也返回不遵循关联性或操作顺序的答案

ex)

还返回

mult(add(integer(3), integer(2)), integer(1))

返回1+2+3和1+(2+3)的等效值,但它应仅返回前者

有什么办法可以让它工作吗

编辑:更多信息:我只需要实现+,-,*,/,求反(-1,-2等),所有数字都是整数。提示代码的结构将类似于语法

<expression> ::= <expression> + <term>
              |  <expression> - <term>
              |  <term>

      <term> ::= <term> * <factor>
              |  <term> / <factor>
              |  <factor>

    <factor> ::= num
              |  ( <expression> )
::=+
|   - 
|  
::=  * 
|   / 
|  
:=num
|  (  )
只有同时实现了否定


Edit2:我发现了一个用Prolog()编写的语法分析器。是否有一种方法可以修改它以打印语法的左手派生(“打印”的意思是Prolog解释器将输出“T=[正确答案]”)

正确的方法是使用DCGs,但示例语法是左递归的,这不起作用。以下是我们应该做的:

expression(T+E) --> term(T), [plus], expression(E).
expression(T-E) --> term(T), [minus], expression(E).
expression(T)   --> term(T).

term(F*T) --> factor(F), [star], term(T).
term(F/T) --> factor(F), [div], term(T).
term(F)   --> factor(F).

factor(N) --> num(N).
factor(E) --> ['('], expression(E), [')'].

num(N) --> [num(N)], { number(N) }.
这与示例语法之间的关系应该很明显,从左递归到右递归的转换也是如此。我记不起automata类中关于最左端派生的细节,但我认为只有在语法不明确的情况下,它才会起作用,而我不认为这是一个。希望一位真正的计算机科学家会来澄清这一点

我认为除了Prolog将使用的内容外,生成AST没有任何意义。产品左侧括号内的代码是AST建筑代码(例如第一条
表达式//1
规则中的
T+e
)。如果不需要,请相应调整代码

从这里开始,展示您的
parse/2
API非常简单:

parse(L, T) :- phrase(expression(T), L).
因为我们使用的是Prolog自己的结构,所以结果看起来要比实际效果差得多:

?- parse([num(4), star, num(8), div, '(', num(3), plus, num(1), ')'], T).
T = 4* (8/ (3+1)) ;
false.
如果您喜欢使用
write\u canonical/2
,可以显示更多AST-y输出:

?- parse([num(4), star, num(8), div, '(', num(3), plus, num(1), ')'], T),
   write_canonical(T).
*(4,/(8,+(3,1)))
T = 4* (8/ (3+1)) a
?- parse([num(4), star, num(8), div, '(', num(3), plus, num(1), ')'], T),
   Result is T.
T = 4* (8/ (3+1)),
Result = 8 ;
false.
部分
*(4,/(8,+(3,1))
编写规范/1
的结果。您可以直接使用
is/2
对其进行评估:

?- parse([num(4), star, num(8), div, '(', num(3), plus, num(1), ')'], T),
   write_canonical(T).
*(4,/(8,+(3,1)))
T = 4* (8/ (3+1)) a
?- parse([num(4), star, num(8), div, '(', num(3), plus, num(1), ')'], T),
   Result is T.
T = 4* (8/ (3+1)),
Result = 8 ;
false.

在修复程序之前,请查看您是如何发现问题的!您假设一个特定的句子正好有一个语法树,但您有两个语法树。所以本质上,Prolog帮助您找到了bug

在Prolog中,这是一个非常有用的调试策略:查看所有答案

接下来是对语法进行编码的具体方式。事实上,您做了一件非常聪明的事情:您本质上编码了一个左递归语法——然而您的程序终止于一个固定长度的列表!这是因为在每个递归中,你必须在中间至少有一个元素作为运算符。因此,对于每个递归,必须至少有一个元素。那很好。然而,这一战略本身就非常低效。因为,对于规则的每个应用程序,它都必须考虑所有可能的分区。

另一个缺点是,您无法再从语法树生成句子。也就是说,如果您将您的定义用于:

?- parse(S, add(add(integer(1),integer(2)),integer(3))).
有两个原因:第一,目标
T=add(…,…)
太晚了。只需将它们放在
append/3
目标前面的开头。但更有趣的是,现在
append/3
并没有终止。以下是相关的(请参阅链接了解更多信息)

所以这一小部分必须改变。注意,规则“知道”它需要一个终端符号,唉,终端出现得太晚了。要是它出现在递归之前就好了!但事实并非如此

解决这个问题的一般方法是:添加另一对参数来编码长度

parse(T, L) :- phrase(expr(T, L,[]), L). expr(integer(X), [_|S],S) --> [num(X)]. expr(add(L,R), [_|S0],S) --> expr(L, S0,S1), [plus], expr(R, S1,S). expr(multiply(L,R), [_|S0],S) --> expr(L, S0,S1), [star], expr(R, S1,S). 解析(T,L):- 短语(expr(T,L,[]),L)。 expr(整数(X),[u124; S],S-->[num(X)]。 expr(添加(L,R),[|S0],S)-->expr(L,S0,S1),[plus],expr(R,S1,S)。 expr(乘法(L,R),[|S0],S)-->expr(L,S0,S1),[star],expr(R,S1,S)。 这是一个非常通用的方法,如果您的语法不明确,或者您不知道您的语法是否不明确,那么这个方法特别有趣。简单地让Prolog为您思考

删除将驱使您使用基于DCG的语法

但有一种有趣的替代方法:实现自底向上的解析

这在Prolog中有多难?正如佩雷拉和希伯在他们精彩的书中所展示的那样 “Prolog和自然语言分析”非常简单:来自第6.5章

Prolog默认情况下提供了一个自顶向下、从左到右的回溯解析算法 DCGs

众所周知,这种自上而下的解析算法将循环使用 左递归规则(参见程序2.3的示例)

虽然技术是有用的- 由于能够从上下文无关语法中删除左递归,这些技术并不适用 很容易推广到DCG,而且它们可以通过 大因素

作为替代,我们可以考虑实现自底向上的解析方法。 直接在Prolog中。在各种可能性中,我们将在这里考虑左拐角。 方法,以适应DCGs

为了便于编程,左角DCG解释器的输入语法表示为DCG符号的细微变化。右手边 规则以列表的形式给出,而不是文本的连词。因此,规则是单位子句 形式的,例如

终端由单词(w,PT)形式的字典单位子句引入

考虑在上课前完成讲座 expr(integer(X)) --> [num(X)]. expr(add(L,R)) --> expr(L), [plus], expr(R). expr(multiply(L,R)) --> expr(L), [star], expr(R). expr(integer(X)) --> {false}, [num(X)]. expr(add(L,R)) --> expr(L), {false}, [plus], expr(R). expr(multiply(L,R)) --> {false}expr(L), [star], expr(R). parse(T, L) :- phrase(expr(T, L,[]), L). expr(integer(X), [_|S],S) --> [num(X)]. expr(add(L,R), [_|S0],S) --> expr(L, S0,S1), [plus], expr(R, S1,S). expr(multiply(L,R), [_|S0],S) --> expr(L, S0,S1), [star], expr(R, S1,S).
s ---> [np, vp].
optrel ---> [].
:- op(150, xfx, ---> ).

parse(Phrase) -->
    leaf(SubPhrase),
    lc(SubPhrase, Phrase).

leaf(Cat) --> [Word], {word(Word,Cat)}.
leaf(Phrase) --> {Phrase ---> []}.

lc(Phrase, Phrase) --> [].

lc(SubPhrase, SuperPhrase) -->
    {Phrase ---> [SubPhrase|Rest]},
    parse_rest(Rest),
    lc(Phrase, SuperPhrase).

parse_rest([]) --> [].
parse_rest([Phrase|Phrases]) -->
    parse(Phrase),
    parse_rest(Phrases).

% that's all! fairly easy, isn't it ?

% here start the grammar: replace with your one, don't worry about Left Recursion
e(sum(L,R)) ---> [e(L),sum,e(R)].
e(num(N)) ---> [num(N)].

word(N, num(N)) :- integer(N).
word(+, sum).
phrase(parse(P), [1,+,3,+,1]).
P = e(sum(sum(num(1), num(3)), num(1)))