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

时间:2013-12-11 05:16:04

标签: parsing prolog dcg failure-slice

我必须编写解析(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)

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

也会返回

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

parse([num(1), plus, num(2), plus, num(3)], T)
当它应该只返回前者时,

返回1 + 2 + 3和1+(2 + 3)的等价物。

我有办法让这个工作吗?

编辑:更多信息:我只需要实现+, - ,*,/,否定(-1,-2等),所有数字都是整数。给出了一个提示,即代码的结构类似于语法

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

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

    <factor> ::= num
              |  ( <expression> )

只有实施了否定。

Edit2:我找到了一个用Prolog(http://www.cs.sunysb.edu/~warren/xsbbook/node10.html)编写的语法分析器。有没有办法可以修改它来打印语法的左手推导(&#34; print&#34;在Prolog解释器将输出的意义上#34; T = [正确的答案]&#34; )

3 个答案:

答案 0 :(得分:4)

在修复程序之前,先看看你是如何识别问题的!您假设某个特定句子只有一个语法树,但您有两个。基本上,Prolog帮助您找到了错误!

这是Prolog中非常有用的调试策略:查看所有答案。

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

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

?- parse(S, add(add(integer(1),integer(2)),integer(3))).

有两个原因:第一个原因是目标T = add(...,...)为时已晚。只需将它们放在append/3目标前面的开头即可。但更有趣的是现在append/3没有终止。以下是相关的failure-slice(有关详情,请参阅相关链接)。

parse([num(X)], integer(X)) :- false.
parse(Tkns, T) :-
  (  T = add(T1,T2),
     append(E1, [plus|E2], Tkns), false,
     parse(E1, T1),
     parse(E2, T2),
  ;  false, T = multiply(T1,T2),
     append(E1, [star|E2], Tkns),
     parse(E1, T1),
     parse(E2, T2),     
  ).
@DanielLyons已经为您提供了“传统”解决方案,该解决方案需要使用正式语言进行各种证明。但是我会坚持你在你的程序中编码的语法 - 翻译成DCG - 读取:

expr(integer(X)) --> [num(X)].
expr(add(L,R)) --> expr(L), [plus], expr(R).
expr(multiply(L,R)) --> expr(L), [star], expr(R).

将此语法与?- phrase(expr(T),[num(1),plus,num(2),plus,num(3)]).一起使用时,它不会终止。这是相关的切片:

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).

如果您的语法含糊不清,或者您不知道您的语法是否含糊不清,这是一种非常通用的方法。只需让Prolog为您做好准备!

答案 1 :(得分:4)

删除left recursion将推动您使用基于DCG的语法。

但是有一种有趣的替代方法:实现自下而上解析。

Prolog有多难?好吧,正如Pereira和Shieber在他们精彩的书中所展示的那样 “Prolog和自然语言分析”非常简单:从第6.5章

开始
  

Prolog默认提供自上而下,从左到右,回溯解析算法   DCG中。

     

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

     

虽然技术有用 -   能够从无上下文语法中删除左递归,这些技术不是   很容易推广到DCG,而且它们可以增加语法大小   大因素。

     

作为替代方案,我们可以考虑实施自下而上的解析方法   直接在Prolog。在各种可能性中,我们将在这里考虑左下角   其中一种方法适用于DCG。

     

为方便编程,左角DCG解释器的输入语法以DCG符号的略微变化表示。右边的   规则是作为列表而不是文字的连词给出的。因此规则是单位条款   形式,例如,

s ---> [np, vp].

optrel ---> [].
  

终端由表单字(w,PT)的字典单元子句引入。

显然,考虑完成讲座,本书为freely available(参见info page中的最后一本书)。

现在让我们尝试编写一个自下而上的处理器:

:- 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))) 

注意使用的左递归语法是e ::= e + e | num

答案 2 :(得分:1)

正确的方法是使用DCG,但你的示例语法是左递归的,这是行不通的。这是:

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) }.

这与你的示例语法之间的关系应该是显而易见的,从左递归到右递归的转换也应该是显而易见的。我不记得我的自动化课程中关于最左派的推导的细节,但我认为只有在语法含糊不清的情况下它才会起作用,我认为这不是。希望真正的计算机科学家会出现并澄清这一点。

除了Prolog将使用的AST之外,我认为没有任何意义。生产左侧括号内的代码是AST构建代码(例如,第一个T+E规则中的expression//1)。如果不希望这样,请相应地调整代码。

从这里开始,展示您的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_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

部分*(4,/(8,+(3,1)))write_canonical/1的结果。您可以使用is/2

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